Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db2a6b84a9 | |||
| 223ff41145 | |||
| 604f23384b | |||
| 011d2d5740 | |||
| 5b1e179e88 | |||
| 6332534ea1 | |||
| 36f0907881 | |||
| 7cd871a419 | |||
| d428db704e | |||
| 9bf1c09457 | |||
| 92f16846ab | |||
| 1de1aeb2c7 | |||
| b177dd1681 | |||
| aeaa27cb99 | |||
| 3c44a1c969 | |||
| 384e52b2c3 | |||
| 7b24ea9575 | |||
| 282214f8bc | |||
| bbf4c24195 | |||
| 407c8c6c17 | |||
| 407da040ab | |||
| 275560ecf3 | |||
| 846dbea2b8 | |||
| 8dbbc80887 | |||
| 8fea655854 | |||
| caebe08cf2 | |||
| 859b22db8e | |||
| 79dae4ee03 | |||
| 852273eff5 | |||
| d26dfbba70 | |||
| fa317014a7 | |||
| cb750e03a9 | |||
| 0f84158e1e | |||
| dc6734db43 | |||
| bcd7c2a1d8 | |||
| d8d3f9431b | |||
| 2875c62cfe | |||
| 75a430d431 | |||
| 49f1516b9e | |||
| b439acac16 | |||
| 506f79a45a | |||
| 391e240b49 | |||
| aaef626684 | |||
| c7339dc248 | |||
| 7540a2a1a8 | |||
| 6487d0ddd4 | |||
| 9ac18855fc | |||
| 0dc0f40cba | |||
| 2bbe88f49a | |||
| f86231353f | |||
| 5483176be4 | |||
| 8118713706 | |||
| cea8c93616 | |||
| 5100fe04cc | |||
| a4fd008a24 | |||
| ec7414c356 | |||
| 3c9b0a7b2f | |||
| 4deea9d667 | |||
| 97cb075afe | |||
| e605cad688 | |||
| 07ee01f435 | |||
| b278bf9819 | |||
| 15f51c5151 | |||
| 9b620b31bd | |||
| 853980cdfd | |||
| 2e346a4e4a | |||
| 6277330017 | |||
| 1d4adadaa7 | |||
| 051af10402 | |||
| 5beaf561df | |||
| 04b739e637 | |||
| efbe7237f2 | |||
| 21ade25fbb | |||
| a4ec819e7d | |||
| 65f89f9480 | |||
| da31b978bc | |||
| 4876a0e3ab | |||
| 60cbb67877 | |||
| 7501c19be4 | |||
| 5019793f17 | |||
| 16d8cc22d1 | |||
| 91f442bd2f | |||
| c2eec8c74a | |||
| 0afa37c8ea | |||
| e49f140233 | |||
| f6a37f4601 | |||
| 76e91c3366 | |||
| 539c1ee6e7 | |||
| 99bf2fc0cb | |||
| ff08d06c45 | |||
| 39fb25cbb9 | |||
| 41fffc0fa5 | |||
| 771d8fb5e7 | |||
| 89944b66c4 | |||
| bbc9ec213a | |||
| 3afd6fd427 | |||
| e449944e3b | |||
| b434b05215 | |||
| bce9da4343 | |||
| 403d9d08e2 | |||
| 1cd572125e | |||
| 40752c2235 | |||
| 40838e187c | |||
| d0cfd498ee | |||
| ed5fe948d1 | |||
| 27871899b0 | |||
| 863c54f24f | |||
| abf886e877 | |||
| c73d1973bc | |||
| a7cdfb84b0 | |||
| 0b378601d9 | |||
| 2ec61ab4ff | |||
| 21406a5e7d | |||
| fd711093b1 | |||
| f8630a09df | |||
| f354f986ca | |||
| db28d93676 | |||
| a92970236c | |||
| 745f9e9d79 | |||
| f103bdb9d8 | |||
| 300eb313dd | |||
| 0b0c8027a3 | |||
| ab0c6a8f66 | |||
| 9e81d39194 | |||
| 8a6f56a89e | |||
| 3a528c7039 | |||
| 31ea4548d7 | |||
| 9258cdde84 | |||
| ec214a009e | |||
| 251adc1eec | |||
| bf51de3805 | |||
| 6a4a7483aa | |||
| dc795eb7ee | |||
| f541133569 | |||
| 96ebc72dd1 | |||
| 1f56a9b9fe | |||
| 9696f418fd | |||
| 36a3d5943a | |||
| 05df3c3805 | |||
| ead2cb13b7 |
@@ -1,7 +0,0 @@
|
||||
dist: xenial
|
||||
|
||||
language: java
|
||||
|
||||
sudo: true
|
||||
|
||||
script: "./mvnw -Pjava11 clean dependency:list test -Dsort -U -B"
|
||||
@@ -1,27 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io.
|
||||
All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
|
||||
Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
|
||||
+1
-1
@@ -123,7 +123,7 @@ Add the Maven dependency:
|
||||
// Always change both files!
|
||||
**Compatibility Matrix**
|
||||
|
||||
The compatibility between Spring Data Elasticsearch, Elasticsearch client drivers and Spring Boot versions can be found in the https://docs.spring.io/spring-data/elasticsearch/docs/3.2.0.RC3/reference/html/#preface.versions[reference documentation].
|
||||
The compatibility between Spring Data Elasticsearch, Elasticsearch client drivers and Spring Boot versions can be found in the https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#preface.versions[reference documentation].
|
||||
|
||||
To use the Release candidate versions of the upcoming major version, use our Maven milestone repository and declare the appropriate dependency version:
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>4.0.0.M3</version>
|
||||
<version>4.1.0-M1</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.3.0.M3</version>
|
||||
<version>2.4.0-M1</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
<properties>
|
||||
<commonslang>2.6</commonslang>
|
||||
<elasticsearch>7.5.2</elasticsearch>
|
||||
<elasticsearch>7.7.1</elasticsearch>
|
||||
<log4j>2.9.1</log4j>
|
||||
<springdata.commons>2.3.0.M3</springdata.commons>
|
||||
<netty>4.1.39.Final</netty>
|
||||
<springdata.commons>2.4.0-M1</springdata.commons>
|
||||
<netty>4.1.50.Final</netty>
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
</properties>
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>2.25.1</version>
|
||||
<version>2.26.3</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- these exclusions are needed because of Elasticsearch JarHell-->
|
||||
@@ -292,6 +292,23 @@
|
||||
</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
|
||||
@@ -304,6 +321,7 @@
|
||||
<useFile>false</useFile>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
<systemPropertyVariables>
|
||||
<es.set.netty.runtime.available.processors>false</es.set.netty.runtime.available.processors>
|
||||
|
||||
@@ -5,13 +5,14 @@ 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-2019 The original author(s).
|
||||
(C) 2013-2020 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
|
||||
@@ -23,7 +24,15 @@ 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
|
||||
|
||||
@@ -35,4 +44,5 @@ include::{spring-data-commons-docs}/repository-namespace-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[]
|
||||
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[]
|
||||
include::reference/migration-guides.adoc[]
|
||||
:leveloffset: -1
|
||||
|
||||
@@ -35,8 +35,9 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
|
||||
[cols="^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
|
||||
| Neumannfootnote:cdv[Currently in development] |4.0.xfootnote:cdv[]|7.5.2 |2.3.xfootnote:cdv[]
|
||||
| Moore | 3.2.x |6.8.4 | 2.2.x
|
||||
| 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.7.1 |2.3.xfootnote:cdv[]
|
||||
| Neumann | 4.0.x | 7.6.2 |2.3.x
|
||||
| Moore | 3.2.x |6.8.10 | 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[]
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
[[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.
|
||||
@@ -143,40 +143,47 @@ 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.
|
||||
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts, headers and other parameters.
|
||||
|
||||
.Client Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
// optional if Basic Auhtentication is needed
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add("es-security-runas-user", "some-user") <1>
|
||||
httpHeaders.add("some-header", "on every request") <1>
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291") <2>
|
||||
.withProxy("localhost:8888") <3>
|
||||
.withPathPrefix("ela") <4>
|
||||
.withConnectTimeout(Duration.ofSeconds(5)) <5>
|
||||
.withSocketTimeout(Duration.ofSeconds(3)) <6>
|
||||
.useSsl() <7>
|
||||
.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;
|
||||
})
|
||||
. // ... 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 set a proxy footnote:notreactive[not yet implemented for the reactive client].
|
||||
<4> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
|
||||
<5> Set the connection timeout. Default is 10 sec.
|
||||
<6> Set the socket timeout. Default is 5 sec.
|
||||
<7> Optionally 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.
|
||||
====
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
[[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`
|
||||
|
||||
|===
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
[[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.
|
||||
@@ -0,0 +1,33 @@
|
||||
[[elasticsearch-migration-guide-4.0-4.1]]
|
||||
= Upgrading from 4.0.x to 4.1.x
|
||||
|
||||
This section describes breaking changes from version 4.0.x to 4.1.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
.Definition of the id property
|
||||
It is possible to define a property of en entity as the id property by naming it either `id` or `document`.
|
||||
This behaviour is now deprecated and will produce a warning.
|
||||
PLease us the `@Id` annotation to mark a property as being the id property.
|
||||
|
||||
In the `ReactiveElasticsearchClient.Indices` interface the `updateMapping` methods are deprecated in favour of the `putMapping` methods.
|
||||
They do the same, but `putMapping` is consistent with the naming in the Elasticsearch API:
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.removal]]
|
||||
== Removals
|
||||
|
||||
.Type mappings
|
||||
The _type mappings_ parameters of the `@Document` annotation and the `IndexCoordinates` object were removed.
|
||||
They had been deprecated in Spring Data Elasticsearch 4.0 and their values weren't used anymore.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
=== Return types of ReactiveElasticsearchClient.Indices methods
|
||||
|
||||
The methods in the `ReactiveElasticsearchClient.Indices` were not used up to now.
|
||||
With the introduction of the `ReactiveIndexOperations` it became necessary to change some of the return types:
|
||||
|
||||
* the `createIndex` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful index creation.
|
||||
* the `updateMapping` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful mappings storage.
|
||||
@@ -27,59 +27,65 @@ Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, Sample
|
||||
[[elasticsearch.scroll]]
|
||||
== Using Scroll For Big Result Set
|
||||
|
||||
Elasticsearch has a scroll API for getting big result set in chunks. `ElasticsearchOperations` has startScroll and continueScroll methods that can be used as below.
|
||||
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.
|
||||
|
||||
.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();
|
||||
|
||||
ScrolledPage<SampleEntity> scroll = operations.startScroll(1000, searchQuery, SampleEntity.class, index);
|
||||
|
||||
String scrollId = scroll.getScrollId();
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
while (scroll.hasContent()) {
|
||||
sampleEntities.addAll(scroll.getContent());
|
||||
scrollId = scroll.getScrollId();
|
||||
scroll = operations.continueScroll(scrollId, 1000, SampleEntity.class);
|
||||
}
|
||||
operations.clearScroll(scrollId);
|
||||
----
|
||||
====
|
||||
|
||||
`ElasticsearchOperations` additionally has the stream method which wraps the scan and scroll operations into a CloseableIterator.
|
||||
|
||||
.Using stream
|
||||
====
|
||||
[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();
|
||||
|
||||
CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class, index);
|
||||
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
|
||||
|
||||
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
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
== New in Spring Data Elasticsearch 4.0
|
||||
|
||||
* Uses Spring 5.2.
|
||||
* Upgrade to Elasticsearch 7.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 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
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
|
||||
|
||||
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore.
|
||||
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
|
||||
|
||||
The main reasons for the removal of the Jackson based mapper are:
|
||||
|
||||
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`. This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
|
||||
* Custom field types and formats also need to be stored into the Elasticsearch index mappings. The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
|
||||
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
|
||||
|
||||
Using the `MappingElasticsearchConverter` now covers all these cases.
|
||||
|
||||
|
||||
[[elasticsearch.mapping.meta-model]]
|
||||
@@ -20,31 +23,34 @@ This allows to register `Converter` instances for specific domain type mapping.
|
||||
[[elasticsearch.mapping.meta-model.annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The metadata is taken from the entities properties which can be annotated.
|
||||
The `MappingElasticsearchConverter` uses metadata to drive the mapping of objects to documents. The metadata is taken from the entity's properties which can be annotated.
|
||||
|
||||
The following annotations are available:
|
||||
|
||||
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. The most important attributes are:
|
||||
** `indexName`: the name of the index to store this entity in
|
||||
** `type`: the mapping type. If not set, the lowercased simple name of the class is used.
|
||||
** `type`: [line-through]#the mapping type. If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
|
||||
** `shards`: the number of shards for the index.
|
||||
** `replicas`: the number of replicas for the index.
|
||||
** `refreshIntervall`: Refresh interval for the index. Used for index creation. Default value is _"1s"_.
|
||||
** `indexStoreType`: Index storage type for the index. Used for index creation. Default value is _"fs"_.
|
||||
** `createIndex`: Configuration whether to create an index on repository bootstrapping. Default value is _true_.
|
||||
** `versionType`: Configuration of version management. Default value is _EXTERNAL_.
|
||||
|
||||
* `@Id`: Applied at the field level to mark the field used for identity purpose.
|
||||
* `@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
|
||||
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
|
||||
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
|
||||
* `@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:
|
||||
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
|
||||
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
|
||||
** `type`: the field type, can be one of _Text, Integer, Long, Date, Float, Double, Boolean, Object, Auto, Nested, Ip, Attachment, Keyword_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
|
||||
** `format` and `pattern` custom definitions for the _Date_ type.
|
||||
** `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.
|
||||
** `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.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules]]
|
||||
@@ -206,25 +212,14 @@ public class Config extends AbstractElasticsearchConfiguration {
|
||||
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public EntityMapper entityMapper() {
|
||||
|
||||
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
|
||||
elasticsearchMappingContext(), new DefaultConversionService());
|
||||
entityMapper.setConversions(elasticsearchCustomConversions()); <1>
|
||||
|
||||
return entityMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
|
||||
return new ElasticsearchCustomConversions(
|
||||
Arrays.asList(new AddressToMap(), new MapToAddress())); <2>
|
||||
Arrays.asList(new AddressToMap(), new MapToAddress())); <1>
|
||||
}
|
||||
|
||||
@WritingConverter <3>
|
||||
@WritingConverter <2>
|
||||
static class AddressToMap implements Converter<Address, Map<String, Object>> {
|
||||
|
||||
@Override
|
||||
@@ -238,7 +233,7 @@ public class Config extends AbstractElasticsearchConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter <4>
|
||||
@ReadingConverter <3>
|
||||
static class MapToAddress implements Converter<Map<String, Object>, Address> {
|
||||
|
||||
@Override
|
||||
@@ -258,8 +253,7 @@ public class Config extends AbstractElasticsearchConfiguration {
|
||||
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
|
||||
}
|
||||
----
|
||||
<1> Register `ElasticsearchCustomConversions` with the `EntityMapper`.
|
||||
<2> Add `Converter` implementations.
|
||||
<3> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
|
||||
<4> Set up the `Converter` used for reading `DomainType` from search result.
|
||||
<1> 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.
|
||||
====
|
||||
|
||||
@@ -41,7 +41,7 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
|
||||
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
|
||||
return new ElasticsearchTemplate(elasticsearchClient(), entityMapper());
|
||||
return new ElasticsearchTemplate(elasticsearchClient());
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -125,7 +125,7 @@ include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
|
||||
|
||||
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 ike the _maxScore_ or requested aggregations.
|
||||
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:
|
||||
@@ -133,7 +133,8 @@ Contains the following information:
|
||||
* Id
|
||||
* Score
|
||||
* Sort Values
|
||||
* Highligth fields
|
||||
* Highlight fields
|
||||
* Inner hits (this is an embedded `SearchHits` object containing eventually returned inner hits)
|
||||
* The retrieved entity of type <T>
|
||||
|
||||
.SearchHits<T>
|
||||
@@ -145,3 +146,12 @@ Contains the following information:
|
||||
* 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.
|
||||
|
||||
|
||||
@@ -273,8 +273,9 @@ Repository methods can be defined to have the following return types for returni
|
||||
* `List<T>`
|
||||
* `Stream<T>`
|
||||
* `SearchHits<T>`
|
||||
* `List<SearchHits<T>>`
|
||||
* `Stream<>>SearchHit<T>>`
|
||||
* `List<SearchHit<T>>`
|
||||
* `Stream<SearchHit<T>>`
|
||||
* `SearchPage<T>`
|
||||
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
== Using @Query Annotation
|
||||
@@ -284,8 +285,21 @@ Repository methods can be defined to have the following return types for returni
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
|
||||
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
|
||||
Page<Book> findByName(String name,Pageable pageable);
|
||||
}
|
||||
----
|
||||
The String that is set as the annotation argument must be a valid Elasticsearch JSON query. It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter _John_, it would produce the following query body:
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"query": {
|
||||
"match": {
|
||||
"name": {
|
||||
"query": "John"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[[elasticsearch.migration]]
|
||||
= Appendix E: Migration Guides
|
||||
|
||||
// line breaks required otherwise the TOC breaks due to joining of first/last lines.
|
||||
:leveloffset: +1
|
||||
include::elasticsearch-migration-guide-3.2-4.0.adoc[]
|
||||
|
||||
include::elasticsearch-migration-guide-4.0-4.1.adoc[]
|
||||
:leveloffset: -1
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.1
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import org.springframework.lang.Nullable;
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException}
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchException extends RuntimeException {
|
||||
|
||||
@Nullable private Map<String, String> failedDocuments;
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.UncategorizedDataAccessException;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.0
|
||||
*/
|
||||
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
|
||||
|
||||
public UncategorizedElasticsearchException(String msg) {
|
||||
super(msg, null);
|
||||
}
|
||||
|
||||
public UncategorizedElasticsearchException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,7 +1,5 @@
|
||||
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;
|
||||
@@ -9,6 +7,8 @@ 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,4 +26,5 @@ public @interface CompletionContext {
|
||||
|
||||
String precision() default "";
|
||||
|
||||
String path() default "";
|
||||
}
|
||||
|
||||
@@ -16,16 +16,58 @@
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
|
||||
*
|
||||
* @author Jakub Vavrik
|
||||
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
|
||||
* @author Tim te Beek
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public enum DateFormat {
|
||||
none, custom, basic_date, basic_date_time, basic_date_time_no_millis, basic_ordinal_date, basic_ordinal_date_time,
|
||||
basic_ordinal_date_time_no_millis, basic_time, basic_time_no_millis, basic_t_time, basic_t_time_no_millis,
|
||||
basic_week_date, basic_week_date_time, basic_week_date_time_no_millis, date, date_hour, date_hour_minute,
|
||||
date_hour_minute_second, date_hour_minute_second_fraction, date_hour_minute_second_millis, date_optional_time,
|
||||
date_time, date_time_no_millis, hour, hour_minute, hour_minute_second, hour_minute_second_fraction,
|
||||
hour_minute_second_millis, ordinal_date, ordinal_date_time, ordinal_date_time_no_millis, time, time_no_millis,
|
||||
t_time, t_time_no_millis, week_date, week_date_time, weekDateTimeNoMillis, week_year, weekyearWeek,
|
||||
weekyearWeekDay, year, year_month, year_month_day
|
||||
none, //
|
||||
custom, //
|
||||
basic_date, //
|
||||
basic_date_time, //
|
||||
basic_date_time_no_millis, //
|
||||
basic_ordinal_date, //
|
||||
basic_ordinal_date_time, //
|
||||
basic_ordinal_date_time_no_millis, //
|
||||
basic_time, //
|
||||
basic_time_no_millis, //
|
||||
basic_t_time, //
|
||||
basic_t_time_no_millis, //
|
||||
basic_week_date, //
|
||||
basic_week_date_time, //
|
||||
basic_week_date_time_no_millis, //
|
||||
date, //
|
||||
date_hour, //
|
||||
date_hour_minute, //
|
||||
date_hour_minute_second, //
|
||||
date_hour_minute_second_fraction, //
|
||||
date_hour_minute_second_millis, //
|
||||
date_optional_time, //
|
||||
date_time, //
|
||||
date_time_no_millis, //
|
||||
epoch_millis, //
|
||||
epoch_second, //
|
||||
hour, //
|
||||
hour_minute, //
|
||||
hour_minute_second, //
|
||||
hour_minute_second_fraction, //
|
||||
hour_minute_second_millis, //
|
||||
ordinal_date, //
|
||||
ordinal_date_time, //
|
||||
ordinal_date_time_no_millis, //
|
||||
time, //
|
||||
time_no_millis, //
|
||||
t_time, //
|
||||
t_time_no_millis, //
|
||||
week_date, //
|
||||
week_date_time, //
|
||||
week_date_time_no_millis, //
|
||||
weekyear, //
|
||||
weekyear_week, //
|
||||
weekyear_week_day, //
|
||||
year, //
|
||||
year_month, //
|
||||
year_month_day //
|
||||
}
|
||||
|
||||
@@ -53,17 +53,6 @@ public @interface Document {
|
||||
*/
|
||||
String indexName();
|
||||
|
||||
/**
|
||||
* Mapping type name. <br/>
|
||||
* deprecated as Elasticsearch does not support this anymore
|
||||
* (@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/removal-of-types.html">Elastisearch removal of types documentation</a>) and will remove it in
|
||||
* Elasticsearch 8.
|
||||
*
|
||||
* @deprecated since 4.0
|
||||
*/
|
||||
@Deprecated
|
||||
String type() default "";
|
||||
|
||||
/**
|
||||
* Use server-side settings when creating the index.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import org.springframework.data.annotation.Persistent;
|
||||
* Elasticsearch dynamic templates mapping.
|
||||
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
|
||||
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
|
||||
* DynamicTemplates annotation is ommited if {@link Mapping} annotation is used.
|
||||
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
|
||||
*
|
||||
* @author Petr Kukral
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @author Kevin Leturc
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Xiao Yu
|
||||
* @author Aleksei Arsenev
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@@ -148,4 +149,9 @@ public @interface Field {
|
||||
* @since 4.0
|
||||
*/
|
||||
double scalingFactor() default 1;
|
||||
|
||||
/**
|
||||
* @since 4.0
|
||||
*/
|
||||
int maxShingleSize() default -1;
|
||||
}
|
||||
|
||||
@@ -21,33 +21,35 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @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
|
||||
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 //
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.lang.annotation.Target;
|
||||
* @author Sascha Woo
|
||||
* @author Xiao Yu
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Aleksei Arsenev
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@@ -117,4 +118,9 @@ public @interface InnerField {
|
||||
* @since 4.0
|
||||
*/
|
||||
double scalingFactor() default 1;
|
||||
|
||||
/**
|
||||
* @since 4.0
|
||||
*/
|
||||
int maxShingleSize() default -1;
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -153,6 +155,7 @@ public interface ClientConfiguration {
|
||||
* @return the path prefix.
|
||||
* @since 4.0
|
||||
*/
|
||||
@Nullable
|
||||
String getPathPrefix();
|
||||
|
||||
/**
|
||||
@@ -168,6 +171,11 @@ public interface ClientConfiguration {
|
||||
*/
|
||||
Function<WebClient, WebClient> getWebClientConfigurer();
|
||||
|
||||
/**
|
||||
* @return the supplier for custom headers.
|
||||
*/
|
||||
Supplier<HttpHeaders> getHeadersSupplier();
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -333,6 +341,19 @@ 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.
|
||||
*
|
||||
|
||||
+15
-3
@@ -21,6 +21,7 @@ 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;
|
||||
@@ -58,7 +59,8 @@ class ClientConfigurationBuilder
|
||||
private @Nullable String password;
|
||||
private @Nullable String pathPrefix;
|
||||
private @Nullable String proxy;
|
||||
private @Nullable Function<WebClient, WebClient> webClientConfigurer;
|
||||
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
|
||||
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -196,7 +198,8 @@ 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");
|
||||
|
||||
@@ -204,6 +207,15 @@ 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()
|
||||
@@ -219,7 +231,7 @@ class ClientConfigurationBuilder
|
||||
}
|
||||
|
||||
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
|
||||
hostnameVerifier, proxy, webClientConfigurer);
|
||||
hostnameVerifier, proxy, webClientConfigurer, headersSupplier);
|
||||
}
|
||||
|
||||
private static InetSocketAddress parse(String hostAndPort) {
|
||||
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -90,7 +91,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, HttpStatus statusCode) {
|
||||
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
|
||||
|
||||
+14
-4
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -47,14 +48,16 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
private final @Nullable SSLContext sslContext;
|
||||
private final Duration soTimeout;
|
||||
private final Duration connectTimeout;
|
||||
private final String pathPrefix;
|
||||
private final @Nullable String pathPrefix;
|
||||
private final @Nullable HostnameVerifier hostnameVerifier;
|
||||
private final String proxy;
|
||||
private final @Nullable 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, String proxy, Function<WebClient, WebClient> webClientConfigurer) {
|
||||
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
|
||||
Function<WebClient, WebClient> webClientConfigurer, Supplier<HttpHeaders> headersSupplier) {
|
||||
|
||||
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
|
||||
this.headers = new HttpHeaders(headers);
|
||||
@@ -66,6 +69,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.proxy = proxy;
|
||||
this.webClientConfigurer = webClientConfigurer;
|
||||
this.headersSupplier = headersSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,6 +107,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
return this.soTimeout;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getPathPrefix() {
|
||||
return this.pathPrefix;
|
||||
@@ -115,6 +120,11 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
|
||||
@Override
|
||||
public Function<WebClient, WebClient> getWebClientConfigurer() {
|
||||
return webClientConfigurer != null ? webClientConfigurer : Function.identity();
|
||||
return webClientConfigurer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<HttpHeaders> getHeadersSupplier() {
|
||||
return headersSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
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;
|
||||
@@ -36,9 +36,10 @@ import org.springframework.util.Assert;
|
||||
* @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 String hosts = "http://localhost:9200";
|
||||
static final String COMMA = ",";
|
||||
@@ -46,12 +47,12 @@ public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>,
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
log.info("Closing elasticSearch client");
|
||||
LOGGER.info("Closing elasticSearch client");
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Error closing ElasticSearch client: ", e);
|
||||
LOGGER.error("Error closing ElasticSearch client: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,11 @@ 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.Optional;
|
||||
import java.util.function.Supplier;
|
||||
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;
|
||||
@@ -55,6 +53,7 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Henrique Amaral
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class RestClients {
|
||||
@@ -86,31 +85,23 @@ public final class RestClients {
|
||||
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
|
||||
Header[] httpHeaders = headers.toSingleValueMap().entrySet().stream()
|
||||
.map(it -> new BasicHeader(it.getKey(), it.getValue())).toArray(Header[]::new);
|
||||
builder.setDefaultHeaders(httpHeaders);
|
||||
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||
}
|
||||
|
||||
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||
|
||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||
Optional<HostnameVerifier> hostNameVerifier = clientConfiguration.getHostNameVerifier();
|
||||
sslContext.ifPresent(clientBuilder::setSSLContext);
|
||||
hostNameVerifier.ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
|
||||
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
|
||||
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));
|
||||
|
||||
if (ClientLogger.isEnabled()) {
|
||||
|
||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||
|
||||
clientBuilder.addInterceptorLast((HttpRequestInterceptor) interceptor);
|
||||
clientBuilder.addInterceptorLast((HttpResponseInterceptor) interceptor);
|
||||
}
|
||||
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration timeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
Builder requestConfigBuilder = RequestConfig.custom();
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
|
||||
@@ -118,6 +109,8 @@ public final class RestClients {
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
Duration timeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
if (!timeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
|
||||
}
|
||||
@@ -133,8 +126,16 @@ 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).collect(Collectors.toList());
|
||||
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +180,6 @@ 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,10 +204,31 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+240
-203
@@ -16,7 +16,9 @@
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
|
||||
import io.netty.handler.ssl.JdkSslContext;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||
@@ -25,8 +27,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.tcp.ProxyProvider;
|
||||
import reactor.netty.tcp.TcpClient;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -34,18 +35,17 @@ 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;
|
||||
@@ -54,10 +54,14 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
@@ -79,7 +83,6 @@ 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.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
@@ -90,18 +93,19 @@ 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.elasticsearch.search.suggest.Suggest;
|
||||
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.RequestConverters;
|
||||
import org.springframework.data.elasticsearch.client.util.NamedXContents;
|
||||
import org.springframework.data.elasticsearch.client.util.ScrollState;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -112,8 +116,6 @@ 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;
|
||||
@@ -128,6 +130,9 @@ 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
|
||||
* @author Thomas Geese
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
@@ -135,6 +140,8 @@ 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
|
||||
@@ -143,10 +150,30 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,14 +204,35 @@ 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,
|
||||
HostProvider hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
|
||||
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
|
||||
return new DefaultReactiveElasticsearchClient(hostProvider);
|
||||
|
||||
DefaultReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator);
|
||||
|
||||
client.setHeadersSupplier(clientConfiguration.getHeadersSupplier());
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
|
||||
@@ -192,14 +240,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
Duration soTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
TcpClient tcpClient = TcpClient.create();
|
||||
HttpClient httpClient = HttpClient.create().compress(true);
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
if (!soTimeout.isNegative()) {
|
||||
tcpClient = tcpClient.doOnConnected(connection -> connection //
|
||||
httpClient = httpClient.doOnConnected(connection -> connection //
|
||||
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
|
||||
}
|
||||
@@ -211,20 +259,24 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
if (hostPort.length != 2) {
|
||||
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
|
||||
}
|
||||
tcpClient = tcpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
||||
httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
||||
.port(Integer.parseInt(hostPort[1])));
|
||||
}
|
||||
|
||||
String scheme = "http";
|
||||
HttpClient httpClient = HttpClient.from(tcpClient);
|
||||
|
||||
if (clientConfiguration.useSsl()) {
|
||||
|
||||
httpClient = httpClient.secure(sslConfig -> {
|
||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||
|
||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||
sslContext.ifPresent(it -> sslConfig.sslContext(new JdkSslContext(it, true, ClientAuth.NONE)));
|
||||
});
|
||||
if (sslContext.isPresent()) {
|
||||
httpClient = httpClient.secure(sslContextSpec -> {
|
||||
sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE,
|
||||
ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false));
|
||||
});
|
||||
} else {
|
||||
httpClient = httpClient.secure();
|
||||
}
|
||||
|
||||
scheme = "https";
|
||||
}
|
||||
@@ -248,8 +300,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
@Override
|
||||
public Mono<Boolean> ping(HttpHeaders headers) {
|
||||
|
||||
return sendRequest(new MainRequest(), RequestCreator.ping(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
return sendRequest(new MainRequest(), requestCreator.ping(), RawActionResponse.class, headers) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next();
|
||||
}
|
||||
|
||||
@@ -260,7 +312,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();
|
||||
}
|
||||
|
||||
@@ -271,7 +323,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();
|
||||
@@ -284,7 +336,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()) //
|
||||
@@ -298,8 +350,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
@Override
|
||||
public Mono<Boolean> exists(HttpHeaders headers, GetRequest getRequest) {
|
||||
|
||||
return sendRequest(getRequest, RequestCreator.exists(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
return sendRequest(getRequest, requestCreator.exists(), RawActionResponse.class, headers) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.next();
|
||||
}
|
||||
|
||||
@@ -309,7 +361,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();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -327,7 +379,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();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -337,7 +389,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();
|
||||
}
|
||||
|
||||
@@ -347,7 +399,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> count(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
return sendRequest(searchRequest, RequestCreator.search(), SearchResponse.class, headers) //
|
||||
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) //
|
||||
.next();
|
||||
@@ -360,11 +415,29 @@ 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)
|
||||
@@ -387,11 +460,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());
|
||||
}
|
||||
|
||||
@@ -444,7 +517,7 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -454,7 +527,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
@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();
|
||||
}
|
||||
|
||||
@@ -464,7 +537,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();
|
||||
}
|
||||
|
||||
@@ -477,31 +550,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
@Override
|
||||
public Mono<Boolean> existsIndex(HttpHeaders headers, GetIndexRequest request) {
|
||||
|
||||
return sendRequest(request, RequestCreator.indexExists(), RawActionResponse.class, headers) //
|
||||
.map(response -> response.statusCode().is2xxSuccessful()) //
|
||||
return sendRequest(request, requestCreator.indexExists(), RawActionResponse.class, headers) //
|
||||
.flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#deleteIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
|
||||
public Mono<Boolean> deleteIndex(HttpHeaders headers, DeleteIndexRequest request) {
|
||||
|
||||
return sendRequest(request, RequestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
return sendRequest(request, requestCreator.indexDelete(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#createIndex(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.create.CreateIndexRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
|
||||
public Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest) {
|
||||
|
||||
return sendRequest(createIndexRequest, RequestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
return sendRequest(createIndexRequest, requestCreator.indexCreate(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -511,7 +578,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();
|
||||
}
|
||||
|
||||
@@ -522,7 +589,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();
|
||||
}
|
||||
|
||||
@@ -533,19 +600,16 @@ 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();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices#updateMapping(org.springframework.http.HttpHeaders, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
public Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
|
||||
return sendRequest(putMappingRequest, RequestCreator.putMapping(), AcknowledgedResponse.class, headers) //
|
||||
.then();
|
||||
return sendRequest(putMappingRequest, requestCreator.putMapping(), AcknowledgedResponse.class, headers) //
|
||||
.map(AcknowledgedResponse::isAcknowledged) //
|
||||
.next();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -555,10 +619,20 @@ 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest) {
|
||||
return sendRequest(getMappingsRequest, requestCreator.getMapping(), GetMappingsResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest) {
|
||||
return sendRequest(getSettingsRequest, requestCreator.getSettings(), GetSettingsResponse.class, headers).next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.ReactiveElasticsearchClientCallback)
|
||||
@@ -638,6 +712,12 @@ 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) {
|
||||
@@ -648,7 +728,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
body::get);
|
||||
|
||||
requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue()));
|
||||
requestBodySpec.body(Mono.fromSupplier(body::get), String.class);
|
||||
requestBodySpec.body(Mono.fromSupplier(body), String.class);
|
||||
} else {
|
||||
ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters());
|
||||
}
|
||||
@@ -685,6 +765,12 @@ 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)) //
|
||||
@@ -715,122 +801,109 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
}
|
||||
|
||||
private static XContentParser createParser(String mediaType, String content) throws IOException {
|
||||
|
||||
return XContentType.fromMediaTypeOrFormat(mediaType) //
|
||||
.xContent() //
|
||||
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
|
||||
.createParser(new NamedXContentRegistry(NamedXContents.getDefaultNamedXContents()),
|
||||
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, content);
|
||||
}
|
||||
|
||||
private static <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
|
||||
private <T> Publisher<? extends T> handleServerError(Request request, ClientResponse response) {
|
||||
|
||||
return Mono.error(
|
||||
new HttpServerErrorException(response.statusCode(), String.format("%s request to %s returned error code %s.",
|
||||
request.getMethod(), request.getEndpoint(), response.statusCode().value())));
|
||||
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)));
|
||||
}
|
||||
|
||||
static class RequestCreator {
|
||||
private <T> Publisher<? extends T> handleClientError(String logId, Request request, ClientResponse response,
|
||||
Class<T> responseType) {
|
||||
|
||||
static Function<SearchRequest, Request> search() {
|
||||
return RequestConverters::search;
|
||||
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<SearchScrollRequest, Request> scroll() {
|
||||
return RequestConverters::searchScroll;
|
||||
}
|
||||
return Mono.just(content);
|
||||
}
|
||||
|
||||
static Function<ClearScrollRequest, Request> clearScroll() {
|
||||
return RequestConverters::clearScroll;
|
||||
}
|
||||
/**
|
||||
* 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<IndexRequest, Request> index() {
|
||||
return RequestConverters::index;
|
||||
}
|
||||
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<GetRequest, Request> get() {
|
||||
return RequestConverters::get;
|
||||
}
|
||||
do {
|
||||
token = parser.nextToken();
|
||||
|
||||
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 -> RequestConverters.deleteByQuery(request);
|
||||
}
|
||||
|
||||
static Function<BulkRequest, Request> bulk() {
|
||||
|
||||
return request -> {
|
||||
|
||||
try {
|
||||
return RequestConverters.bulk(request);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Could not parse request", e);
|
||||
if (parser.currentName().equals("error")) {
|
||||
return ElasticsearchException.failureFromXContent(parser);
|
||||
}
|
||||
};
|
||||
} while (token == XContentParser.Token.FIELD_NAME);
|
||||
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return new ElasticsearchStatusException(content, status);
|
||||
}
|
||||
|
||||
// --> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest) {
|
||||
return sendRequest(searchRequest, requestCreator.search(), SearchResponse.class, headers) //
|
||||
.map(SearchResponse::getSuggest);
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -854,41 +927,5 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@Nullable private String scrollId;
|
||||
|
||||
@Nullable
|
||||
String getScrollId() {
|
||||
return scrollId;
|
||||
}
|
||||
|
||||
List<String> getScrollIds() {
|
||||
|
||||
synchronized (lock) {
|
||||
return Collections.unmodifiableList(new ArrayList<>(pastIds));
|
||||
}
|
||||
}
|
||||
|
||||
void updateScrollId(String scrollId) {
|
||||
|
||||
if (StringUtils.hasText(scrollId)) {
|
||||
|
||||
synchronized (lock) {
|
||||
|
||||
this.scrollId = scrollId;
|
||||
pastIds.add(scrollId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
/**
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
class DefaultRequestCreator implements RequestCreator {}
|
||||
+7
-3
@@ -20,9 +20,11 @@ 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;
|
||||
|
||||
@@ -40,18 +42,20 @@ 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, InetSocketAddress... endpoints) {
|
||||
static HostProvider provider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
|
||||
InetSocketAddress... endpoints) {
|
||||
|
||||
Assert.notNull(clientProvider, "WebClientProvider must not be null");
|
||||
Assert.notEmpty(endpoints, "Please provide at least one endpoint to connect to.");
|
||||
|
||||
if (endpoints.length == 1) {
|
||||
return new SingleNodeHostProvider(clientProvider, endpoints[0]);
|
||||
return new SingleNodeHostProvider(clientProvider, headersSupplier, endpoints[0]);
|
||||
} else {
|
||||
return new MultiNodeHostProvider(clientProvider, endpoints);
|
||||
return new MultiNodeHostProvider(clientProvider,headersSupplier, endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -27,10 +27,12 @@ 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;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -45,11 +47,13 @@ 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, InetSocketAddress... endpoints) {
|
||||
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, 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));
|
||||
@@ -117,15 +121,15 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
.map(ElasticsearchHost::getEndpoint).next();
|
||||
}
|
||||
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, ClientResponse> tuple2) {
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, State> tuple2) {
|
||||
|
||||
State state = tuple2.getT2().statusCode().isError() ? State.OFFLINE : State.ONLINE;
|
||||
State state = tuple2.getT2();
|
||||
ElasticsearchHost elasticsearchHost = new ElasticsearchHost(tuple2.getT1(), state);
|
||||
hosts.put(tuple2.getT1(), elasticsearchHost);
|
||||
return elasticsearchHost;
|
||||
}
|
||||
|
||||
private Flux<Tuple2<InetSocketAddress, ClientResponse>> nodes(@Nullable State state) {
|
||||
private Flux<Tuple2<InetSocketAddress, State>> nodes(@Nullable State state) {
|
||||
|
||||
return Flux.fromIterable(hosts()) //
|
||||
.filter(entry -> state == null || entry.getState().equals(state)) //
|
||||
@@ -133,13 +137,15 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
.flatMap(host -> {
|
||||
|
||||
Mono<ClientResponse> exchange = createWebClient(host) //
|
||||
.head().uri("/").exchange().doOnError(throwable -> {
|
||||
|
||||
.head().uri("/") //
|
||||
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
|
||||
.exchange().doOnError(throwable -> {
|
||||
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
});
|
||||
|
||||
return Mono.just(host).zipWith(exchange);
|
||||
return Mono.just(host).zipWith(exchange
|
||||
.flatMap(it -> it.releaseBody().thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE)));
|
||||
}) //
|
||||
.onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
|
||||
}
|
||||
|
||||
+11
-1
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
@@ -73,4 +74,13 @@ class RawActionResponse extends ActionResponse {
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the response body is released to properly release the underlying connection.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Mono<Void> releaseBody() {
|
||||
return delegate.releaseBody();
|
||||
}
|
||||
}
|
||||
|
||||
+237
-17
@@ -27,9 +27,13 @@ import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.bulk.BulkResponse;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
@@ -47,9 +51,12 @@ import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
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;
|
||||
@@ -61,6 +68,7 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Henrique Amaral
|
||||
* @author Thomas Geese
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
@@ -411,6 +419,84 @@ public interface ReactiveElasticsearchClient {
|
||||
*/
|
||||
Flux<SearchHit> search(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Flux<Suggest> suggest(Consumer<SearchRequest> consumer) {
|
||||
|
||||
SearchRequest request = new SearchRequest();
|
||||
consumer.accept(request);
|
||||
return suggest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
default Flux<Suggest> suggest(SearchRequest searchRequest) {
|
||||
return suggest(HttpHeaders.EMPTY, searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} against the {@literal search} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param searchRequest must not be {@literal null}.
|
||||
* @return the {@link Flux} emitting {@link Suggest suggestions} one by one.
|
||||
* @since 4.1
|
||||
*/
|
||||
Flux<Suggest> suggest(HttpHeaders headers, SearchRequest searchRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link SearchRequest} with aggregations against the {@literal search} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @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.
|
||||
*
|
||||
@@ -628,7 +714,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> deleteIndex(Consumer<DeleteIndexRequest> consumer) {
|
||||
default Mono<Boolean> deleteIndex(Consumer<DeleteIndexRequest> consumer) {
|
||||
|
||||
DeleteIndexRequest request = new DeleteIndexRequest();
|
||||
consumer.accept(request);
|
||||
@@ -644,7 +730,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> deleteIndex(DeleteIndexRequest deleteIndexRequest) {
|
||||
default Mono<Boolean> deleteIndex(DeleteIndexRequest deleteIndexRequest) {
|
||||
return deleteIndex(HttpHeaders.EMPTY, deleteIndexRequest);
|
||||
}
|
||||
|
||||
@@ -658,18 +744,18 @@ public interface ReactiveElasticsearchClient {
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html"> Indices
|
||||
* Delete API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> deleteIndex(HttpHeaders headers, DeleteIndexRequest deleteIndexRequest);
|
||||
Mono<Boolean> deleteIndex(HttpHeaders headers, DeleteIndexRequest deleteIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if
|
||||
* eg. the index already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> createIndex(Consumer<CreateIndexRequest> consumer) {
|
||||
default Mono<Boolean> createIndex(Consumer<CreateIndexRequest> consumer) {
|
||||
|
||||
CreateIndexRequest request = new CreateIndexRequest();
|
||||
consumer.accept(request);
|
||||
@@ -680,12 +766,12 @@ public interface ReactiveElasticsearchClient {
|
||||
* Execute the given {@link CreateIndexRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param createIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if
|
||||
* eg. the index already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> createIndex(CreateIndexRequest createIndexRequest) {
|
||||
default Mono<Boolean> createIndex(CreateIndexRequest createIndexRequest) {
|
||||
return createIndex(HttpHeaders.EMPTY, createIndexRequest);
|
||||
}
|
||||
|
||||
@@ -694,12 +780,12 @@ public interface ReactiveElasticsearchClient {
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param createIndexRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* already exist.
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if
|
||||
* eg. the index already exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html"> Indices
|
||||
* Create API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest);
|
||||
Mono<Boolean> createIndex(HttpHeaders headers, CreateIndexRequest createIndexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link OpenIndexRequest} against the {@literal indices} API.
|
||||
@@ -832,12 +918,58 @@ public interface ReactiveElasticsearchClient {
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
* @deprecated since 4.1, use {@link #putMapping(Consumer)}
|
||||
*/
|
||||
default Mono<Void> updateMapping(Consumer<PutMappingRequest> consumer) {
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(Consumer<PutMappingRequest> consumer) {
|
||||
return putMapping(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param putMappingRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
* @deprecated since 4.1, use {@link #putMapping(PutMappingRequest)}
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(PutMappingRequest putMappingRequest) {
|
||||
return putMapping(putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param putMappingRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
* @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)}
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<Boolean> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest) {
|
||||
return putMapping(headers, putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link PutMappingRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Boolean> putMapping(Consumer<PutMappingRequest> consumer) {
|
||||
|
||||
PutMappingRequest request = new PutMappingRequest();
|
||||
consumer.accept(request);
|
||||
return updateMapping(request);
|
||||
return putMapping(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -849,8 +981,8 @@ public interface ReactiveElasticsearchClient {
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
default Mono<Void> updateMapping(PutMappingRequest putMappingRequest) {
|
||||
return updateMapping(HttpHeaders.EMPTY, putMappingRequest);
|
||||
default Mono<Boolean> putMapping(PutMappingRequest putMappingRequest) {
|
||||
return putMapping(HttpHeaders.EMPTY, putMappingRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -863,7 +995,7 @@ public interface ReactiveElasticsearchClient {
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
|
||||
* Put Mapping API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> updateMapping(HttpHeaders headers, PutMappingRequest putMappingRequest);
|
||||
Mono<Boolean> putMapping(HttpHeaders headers, PutMappingRequest putMappingRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link FlushRequest} against the {@literal indices} API.
|
||||
@@ -905,5 +1037,93 @@ public interface ReactiveElasticsearchClient {
|
||||
* API on elastic.co</a>
|
||||
*/
|
||||
Mono<Void> flushIndex(HttpHeaders headers, FlushRequest flushRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetSettingsResponse> getSettings(Consumer<GetSettingsRequest> consumer) {
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest();
|
||||
consumer.accept(request);
|
||||
return getSettings(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getSettingsRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetSettingsResponse> getSettings(GetSettingsRequest getSettingsRequest) {
|
||||
return getSettings(HttpHeaders.EMPTY, getSettingsRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetSettingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getSettingsRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetSettingsResponse> getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param consumer never {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetMappingsResponse> getMapping(Consumer<GetMappingsRequest> consumer) {
|
||||
|
||||
GetMappingsRequest request = new GetMappingsRequest();
|
||||
consumer.accept(request);
|
||||
return getMapping(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param getMappingsRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
default Mono<GetMappingsResponse> getMapping(GetMappingsRequest getMappingsRequest) {
|
||||
return getMapping(HttpHeaders.EMPTY, getMappingsRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link GetMappingsRequest} against the {@literal indices} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param getMappingsRequest must not be {@literal null}.
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
|
||||
* does not exist.
|
||||
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-mapping.html"> Indices
|
||||
* Flush API on elastic.co</a>
|
||||
* @since 4.1
|
||||
*/
|
||||
Mono<GetMappingsResponse> getMapping(HttpHeaders headers, GetMappingsRequest getMappingsRequest);
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -24,6 +24,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class ReactiveRestClients {
|
||||
@@ -33,6 +34,8 @@ 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) {
|
||||
@@ -41,4 +44,21 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
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.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.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.UncategorizedElasticsearchException;
|
||||
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 UncategorizedElasticsearchException("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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetSettingsRequest, Request> getSettings() {
|
||||
return RequestConverters::getSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
default Function<GetMappingsRequest, Request> getMapping() {
|
||||
return RequestConverters::getMapping;
|
||||
}
|
||||
|
||||
}
|
||||
+16
-9
@@ -19,10 +19,12 @@ 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;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
@@ -35,12 +37,14 @@ 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, InetSocketAddress endpoint) {
|
||||
SingleNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress endpoint) {
|
||||
|
||||
this.clientProvider = clientProvider;
|
||||
this.headersSupplier = headersSupplier;
|
||||
this.endpoint = endpoint;
|
||||
this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN);
|
||||
}
|
||||
@@ -53,22 +57,23 @@ class SingleNodeHostProvider implements HostProvider {
|
||||
public Mono<ClusterInformation> clusterInfo() {
|
||||
|
||||
return createWebClient(endpoint) //
|
||||
.head().uri("/").exchange() //
|
||||
.head().uri("/")
|
||||
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
|
||||
.exchange() //
|
||||
.flatMap(it -> {
|
||||
|
||||
if (it.statusCode().isError()) {
|
||||
state = ElasticsearchHost.offline(endpoint);
|
||||
} else {
|
||||
state = ElasticsearchHost.online(endpoint);
|
||||
}
|
||||
return Mono.just(state);
|
||||
return it.releaseBody().thenReturn(state);
|
||||
}).onErrorResume(throwable -> {
|
||||
|
||||
state = ElasticsearchHost.offline(endpoint);
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
return Mono.just(state);
|
||||
}) //
|
||||
.flatMap(it -> Mono.just(new ClusterInformation(Collections.singleton(it))));
|
||||
.map(it -> new ClusterInformation(Collections.singleton(it)));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -91,14 +96,16 @@ class SingleNodeHostProvider implements HostProvider {
|
||||
return Mono.just(endpoint);
|
||||
}
|
||||
|
||||
return clusterInfo().flatMap(it -> {
|
||||
return clusterInfo().handle((information, sink) -> {
|
||||
|
||||
ElasticsearchHost host = it.getNodes().iterator().next();
|
||||
ElasticsearchHost host = information.getNodes().iterator().next();
|
||||
if (host.isOnline()) {
|
||||
return Mono.just(host.getEndpoint());
|
||||
|
||||
sink.next(host.getEndpoint());
|
||||
return;
|
||||
}
|
||||
|
||||
return Mono.error(() -> new NoReachableHostException(Collections.singleton(host)));
|
||||
sink.error(new NoReachableHostException(Collections.singleton(host)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+32
-2
@@ -38,9 +38,11 @@ import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
import org.elasticsearch.action.explain.ExplainRequest;
|
||||
@@ -751,12 +753,12 @@ public class RequestConverters {
|
||||
}
|
||||
|
||||
Request request = new Request(HttpMethod.PUT.name(),
|
||||
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type()));
|
||||
RequestConverters.endpoint(putMappingRequest.indices(), "_mapping"));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request) //
|
||||
.withTimeout(putMappingRequest.timeout()) //
|
||||
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
|
||||
.withIncludeTypeName(true);
|
||||
.withIncludeTypeName(false);
|
||||
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
@@ -772,6 +774,34 @@ public class RequestConverters {
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getMapping(GetMappingsRequest getMappingsRequest) {
|
||||
String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices();
|
||||
String[] types = getMappingsRequest.types() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.types();
|
||||
|
||||
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping", types));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request);
|
||||
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
|
||||
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
|
||||
parameters.withLocal(getMappingsRequest.local());
|
||||
parameters.withIncludeTypeName(false);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Request getSettings(GetSettingsRequest getSettingsRequest) {
|
||||
String[] indices = getSettingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.indices();
|
||||
String[] names = getSettingsRequest.names() == null ? Strings.EMPTY_ARRAY : getSettingsRequest.names();
|
||||
|
||||
Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_settings", names));
|
||||
|
||||
RequestConverters.Params parameters = new RequestConverters.Params(request);
|
||||
parameters.withIndicesOptions(getSettingsRequest.indicesOptions());
|
||||
parameters.withLocal(getSettingsRequest.local());
|
||||
parameters.withIncludeDefaults(getSettingsRequest.includeDefaults());
|
||||
parameters.withMasterTimeout(getSettingsRequest.masterNodeTimeout());
|
||||
return request;
|
||||
}
|
||||
|
||||
static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-6
@@ -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.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
|
||||
/**
|
||||
@@ -48,9 +47,4 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IndexOperations indexOperations(ElasticsearchOperations elasticsearchOperations) {
|
||||
return elasticsearchOperations.getIndexOperations();
|
||||
}
|
||||
}
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-16
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.config;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -48,7 +46,10 @@ public class ElasticsearchConfigurationSupport {
|
||||
@Bean
|
||||
public ElasticsearchConverter elasticsearchEntityMapper(
|
||||
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
|
||||
return new MappingElasticsearchConverter(elasticsearchMappingContext);
|
||||
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
|
||||
elasticsearchMappingContext);
|
||||
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
|
||||
return elasticsearchConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +60,6 @@ public class ElasticsearchConfigurationSupport {
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
@SneakyThrows
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
|
||||
@@ -100,9 +100,8 @@ public class ElasticsearchConfigurationSupport {
|
||||
*
|
||||
* @see #getMappingBasePackages()
|
||||
* @return never {@literal null}.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
|
||||
protected Set<Class<?>> getInitialEntitySet() {
|
||||
|
||||
Set<Class<?>> initialEntitySet = new HashSet<>();
|
||||
|
||||
@@ -119,9 +118,8 @@ public class ElasticsearchConfigurationSupport {
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
protected Set<Class<?>> scanForEntities(String basePackage) throws ClassNotFoundException {
|
||||
protected Set<Class<?>> scanForEntities(String basePackage) {
|
||||
|
||||
if (!StringUtils.hasText(basePackage)) {
|
||||
return Collections.emptySet();
|
||||
@@ -129,17 +127,20 @@ public class ElasticsearchConfigurationSupport {
|
||||
|
||||
Set<Class<?>> initialEntitySet = new HashSet<>();
|
||||
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
|
||||
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
|
||||
false);
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
|
||||
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
|
||||
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
|
||||
String beanClassName = candidate.getBeanClassName();
|
||||
|
||||
initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(),
|
||||
AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
|
||||
if (beanClassName != null) {
|
||||
try {
|
||||
initialEntitySet.add(
|
||||
ClassUtils.forName(beanClassName, AbstractReactiveElasticsearchConfiguration.class.getClassLoader()));
|
||||
} catch (ClassNotFoundException | LinkageError ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 "";
|
||||
}
|
||||
+152
-76
@@ -17,21 +17,26 @@ 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.common.collect.MapBuilder;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
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.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -48,88 +53,145 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
protected final RequestFactory requestFactory;
|
||||
|
||||
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter) {
|
||||
@Nullable protected final Class<?> boundClass;
|
||||
@Nullable private final IndexCoordinates boundIndex;
|
||||
|
||||
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
|
||||
|
||||
Assert.notNull(boundClass, "boundClass may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
this.boundClass = boundClass;
|
||||
this.boundIndex = null;
|
||||
}
|
||||
|
||||
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
|
||||
|
||||
Assert.notNull(boundIndex, "boundIndex may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
this.boundClass = null;
|
||||
this.boundIndex = boundIndex;
|
||||
}
|
||||
|
||||
protected Class<?> checkForBoundClass() {
|
||||
if (boundClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||
}
|
||||
return boundClass;
|
||||
}
|
||||
|
||||
// region IndexOperations
|
||||
@Override
|
||||
public boolean createIndex(String indexName) {
|
||||
return createIndex(indexName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createIndex(Class<?> clazz) {
|
||||
public boolean create() {
|
||||
|
||||
String indexName = getRequiredPersistentEntity(clazz).getIndexCoordinates().getIndexName();
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
Document settings = null;
|
||||
|
||||
if (hasText(settingPath)) {
|
||||
String settings = ResourceUtil.readFileFromClasspath(settingPath);
|
||||
if (boundClass != null) {
|
||||
Class<?> clazz = boundClass;
|
||||
|
||||
if (hasText(settings)) {
|
||||
return createIndex(indexName, settings);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
settings = loadSettings(settingPath);
|
||||
}
|
||||
|
||||
if (settings == null) {
|
||||
settings = getRequiredPersistentEntity(clazz).getDefaultSettings();
|
||||
}
|
||||
}
|
||||
return createIndex(indexName, getDefaultSettings(getRequiredPersistentEntity(clazz)));
|
||||
|
||||
return doCreate(getIndexCoordinates(), settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createIndex(Class<?> clazz, Object settings) {
|
||||
return createIndex(getRequiredPersistentEntity(clazz).getIndexCoordinates().getIndexName(), settings);
|
||||
public boolean create(Document settings) {
|
||||
return doCreate(getIndexCoordinates(), settings);
|
||||
}
|
||||
|
||||
protected abstract boolean doCreate(IndexCoordinates index, @Nullable Document settings);
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return doDelete(getIndexCoordinates());
|
||||
}
|
||||
|
||||
protected abstract boolean doDelete(IndexCoordinates index);
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return doExists(getIndexCoordinates());
|
||||
}
|
||||
|
||||
protected abstract boolean doExists(IndexCoordinates index);
|
||||
|
||||
@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 boolean deleteIndex(Class<?> clazz) {
|
||||
return deleteIndex(getRequiredPersistentEntity(clazz).getIndexCoordinates().getIndexName());
|
||||
public Map<String, Object> getSettings(boolean includeDefaults) {
|
||||
return doGetSettings(getIndexCoordinates(), includeDefaults);
|
||||
}
|
||||
|
||||
protected abstract Map<String, Object> doGetSettings(IndexCoordinates index, 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());
|
||||
}
|
||||
|
||||
protected abstract List<AliasMetaData> doQueryForAlias(IndexCoordinates index);
|
||||
|
||||
@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 boolean indexExists(Class<?> clazz) {
|
||||
return indexExists(getIndexCoordinatesFor(clazz).getIndexName());
|
||||
public Document createMapping(Class<?> clazz) {
|
||||
return buildMapping(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMapping(Class<?> clazz) {
|
||||
return getMapping(getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putMapping(Class<?> clazz) {
|
||||
return putMapping(clazz, buildMapping(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean putMapping(Class<T> clazz, Object mapping) {
|
||||
return putMapping(getIndexCoordinatesFor(clazz), mapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putMapping(IndexCoordinates index, Class<?> clazz) {
|
||||
return putMapping(index, buildMapping(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(Class<?> clazz) {
|
||||
return getSettings(clazz, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(Class<?> clazz, boolean includeDefaults) {
|
||||
return getSettings(getRequiredPersistentEntity(clazz).getIndexCoordinates().getIndexName(), includeDefaults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(Class<?> clazz) {
|
||||
refresh(getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
protected String buildMapping(Class<?> clazz) {
|
||||
protected Document buildMapping(Class<?> clazz) {
|
||||
|
||||
// load mapping specified in Mapping annotation if present
|
||||
if (clazz.isAnnotationPresent(Mapping.class)) {
|
||||
@@ -139,7 +201,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
|
||||
|
||||
if (!StringUtils.isEmpty(mappings)) {
|
||||
return mappings;
|
||||
return Document.parse(mappings);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
|
||||
@@ -148,7 +210,8 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
// build mapping from field annotations
|
||||
try {
|
||||
return new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
|
||||
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);
|
||||
}
|
||||
@@ -156,28 +219,41 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
|
||||
// endregion
|
||||
|
||||
// region Helper functions
|
||||
private <T> Map getDefaultSettings(ElasticsearchPersistentEntity<T> persistentEntity) {
|
||||
|
||||
if (persistentEntity.isUseServerConfiguration())
|
||||
return new HashMap();
|
||||
|
||||
return 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();
|
||||
}
|
||||
|
||||
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) {
|
||||
@Nullable
|
||||
private Document loadSettings(String settingPath) {
|
||||
if (hasText(settingPath)) {
|
||||
String settingsFile = ResourceUtil.readFileFromClasspath(settingPath);
|
||||
|
||||
Map<String, Object> settings = new HashMap<>();
|
||||
if (hasText(settingsFile)) {
|
||||
return Document.parse(settingsFile);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("settingPath in @Setting has to be defined. Using default instead.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Document convertSettingsResponseToMap(GetSettingsResponse response, String indexName) {
|
||||
|
||||
Document settings = Document.create();
|
||||
|
||||
if (!response.getIndexToDefaultSettings().isEmpty()) {
|
||||
Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName);
|
||||
|
||||
+436
-42
@@ -1,7 +1,23 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -13,7 +29,6 @@ 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.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
|
||||
@@ -21,22 +36,33 @@ import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
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.DeleteQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.GetQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.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;
|
||||
|
||||
@@ -45,21 +71,24 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Sascha Woo
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
protected @Nullable ElasticsearchConverter elasticsearchConverter;
|
||||
protected @Nullable RequestFactory requestFactory;
|
||||
protected @Nullable IndexOperations indexOperations;
|
||||
|
||||
private @Nullable EntityCallbacks entityCallbacks;
|
||||
|
||||
// region Initialization
|
||||
protected void initialize(ElasticsearchConverter elasticsearchConverter, IndexOperations indexOperations) {
|
||||
protected void initialize(ElasticsearchConverter elasticsearchConverter) {
|
||||
|
||||
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null.");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
this.indexOperations = indexOperations;
|
||||
|
||||
VersionInfo.logVersions(getClusterVersion());
|
||||
}
|
||||
|
||||
protected ElasticsearchConverter createElasticsearchConverter() {
|
||||
@@ -70,26 +99,36 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(EntityCallbacks.create(applicationContext));
|
||||
}
|
||||
|
||||
if (elasticsearchConverter instanceof ApplicationContextAware) {
|
||||
((ApplicationContextAware) elasticsearchConverter).setApplicationContext(context);
|
||||
((ApplicationContextAware) elasticsearchConverter).setApplicationContext(applicationContext);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region getter/setter
|
||||
@Override
|
||||
public IndexOperations getIndexOperations() {
|
||||
/**
|
||||
* 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("indexOperations are not initialized");
|
||||
Assert.notNull(entityCallbacks, "entityCallbacks must not be null");
|
||||
|
||||
return indexOperations;
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
|
||||
@Override
|
||||
public <T> T save(T entity) {
|
||||
|
||||
@@ -104,8 +143,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(entity, "entity must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
index(getIndexQuery(entity), index);
|
||||
return entity;
|
||||
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
|
||||
@@ -138,7 +182,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
});
|
||||
}
|
||||
|
||||
return entities;
|
||||
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,31 +191,115 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null.");
|
||||
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
|
||||
DeleteQuery deleteQuery = new DeleteQuery();
|
||||
deleteQuery.setQuery(searchRequest.source().query());
|
||||
|
||||
delete(deleteQuery, index);
|
||||
@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
|
||||
public <T> List<T> multiGet(Query query, Class<T> clazz) {
|
||||
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T queryForObject(GetQuery query, Class<T> clazz) {
|
||||
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 void delete(Query query, Class<?> clazz) {
|
||||
delete(query, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
|
||||
return bulkIndex(queries, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz) {
|
||||
return bulkIndex(queries, bulkOptions, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz) {
|
||||
bulkUpdate(queries, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region SearchOperations
|
||||
@Override
|
||||
public <T> CloseableIterator<T> stream(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
|
||||
return StreamQueries.streamResults(startScroll(scrollTimeInMillis, query, clazz, index),
|
||||
scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz), this::searchScrollClear);
|
||||
public long count(Query query, Class<?> clazz) {
|
||||
return count(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> CloseableIterator<SearchHit<T>> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
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();
|
||||
return StreamQueries.streamResults(searchScrollStart(scrollTimeInMillis, query, clazz, index),
|
||||
scrollId -> searchScrollContinue(scrollId, scrollTimeInMillis, clazz), this::searchScrollClear);
|
||||
|
||||
// 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
|
||||
@@ -183,6 +311,11 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz) {
|
||||
return multiSearch(queries, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
@@ -192,10 +325,42 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
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(elasticsearchConverter.read(clazz, SearchDocumentResponse.from(items[c++].getResponse())));
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse())));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
|
||||
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(classes, "classes must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class<?> clazz = it.next();
|
||||
request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||
}
|
||||
|
||||
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
|
||||
|
||||
List<SearchHits<?>> res = new ArrayList<>(queries.size());
|
||||
int c = 0;
|
||||
Iterator<Class<?>> it1 = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
Class entityClass = it1.next();
|
||||
|
||||
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
|
||||
getIndexCoordinatesFor(entityClass));
|
||||
|
||||
SearchResponse response = items[c++].getResponse();
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(response)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -203,6 +368,12 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Override
|
||||
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(classes, "classes must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
|
||||
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
Iterator<Class<?>> it = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
@@ -215,12 +386,53 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
int c = 0;
|
||||
Iterator<Class<?>> it1 = classes.iterator();
|
||||
for (Query query : queries) {
|
||||
res.add(elasticsearchConverter.read(it1.next(), SearchDocumentResponse.from(items[c++].getResponse())));
|
||||
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);
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
|
||||
return suggest(suggestion, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
@@ -247,9 +459,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return values.toArray(valuesAsArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* @param clazz the entity class
|
||||
* @return the IndexCoordinates defined on the entity.
|
||||
@@ -273,7 +482,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
if (item.isFailed())
|
||||
failedDocuments.put(item.getId(), item.getFailureMessage());
|
||||
}
|
||||
throw new ElasticsearchException(
|
||||
throw new BulkFailureException(
|
||||
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||
+ failedDocuments + ']',
|
||||
failedDocuments);
|
||||
@@ -325,10 +534,195 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
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) {
|
||||
return new IndexQueryBuilder().withObject(entity).withId(getEntityId(entity)).withVersion(getEntityVersion(entity))
|
||||
.build();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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).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).mapScrollHits(response, entities);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
+70
-163
@@ -15,40 +15,31 @@
|
||||
*/
|
||||
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.LinkedHashMap;
|
||||
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.alias.get.GetAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.GetAliasesResponse;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.Response;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.client.indices.CreateIndexRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexRequest;
|
||||
import org.elasticsearch.client.indices.GetMappingsRequest;
|
||||
import org.elasticsearch.client.indices.GetMappingsResponse;
|
||||
import org.elasticsearch.client.indices.PutMappingRequest;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.core.client.support.AliasData;
|
||||
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;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* {@link IndexOperations} implementation using the RestClient.
|
||||
*
|
||||
@@ -58,206 +49,122 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
*/
|
||||
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private RestHighLevelClient client;
|
||||
private final ElasticsearchRestTemplate restTemplate;
|
||||
|
||||
public DefaultIndexOperations(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||
super(elasticsearchConverter);
|
||||
this.client = client;
|
||||
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
|
||||
public boolean createIndex(String indexName, Object settings) {
|
||||
CreateIndexRequest request = requestFactory.createIndexRequest(indexName, settings);
|
||||
try {
|
||||
return client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for creating index: " + request.toString(), e);
|
||||
}
|
||||
protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) {
|
||||
CreateIndexRequest request = requestFactory.createIndexRequest(index, settings);
|
||||
return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteIndex(String indexName) {
|
||||
protected boolean doDelete(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for delete operation");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
if (indexExists(indexName)) {
|
||||
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
|
||||
try {
|
||||
return client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while deleting index request: " + request.toString(), e);
|
||||
}
|
||||
if (doExists(index)) {
|
||||
DeleteIndexRequest deleteIndexRequest = requestFactory.deleteIndexRequest(index);
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean indexExists(String indexName) {
|
||||
GetIndexRequest request = new GetIndexRequest(indexName);
|
||||
try {
|
||||
return client.indices().exists(request, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while for indexExists request: " + request.toString(), e);
|
||||
}
|
||||
protected boolean doExists(IndexCoordinates index) {
|
||||
|
||||
GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index);
|
||||
return restTemplate.execute(client -> client.indices().exists(getIndexRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putMapping(IndexCoordinates index, Object mapping) {
|
||||
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
|
||||
|
||||
Assert.notNull(index, "No index defined for putMapping()");
|
||||
|
||||
PutMappingRequest request = requestFactory.putMappingRequest(index, mapping);
|
||||
try {
|
||||
return client.indices().putMapping(request, RequestOptions.DEFAULT).isAcknowledged();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Failed to put mapping for " + index.getIndexName(), e);
|
||||
}
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().putMapping(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMapping(IndexCoordinates index) {
|
||||
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "No index defined for getMapping()");
|
||||
|
||||
RestClient restClient = client.getLowLevelClient();
|
||||
try {
|
||||
Request request = new Request("GET", '/' + index.getIndexName() + "/_mapping");
|
||||
Response response = restClient.performRequest(request);
|
||||
return convertMappingResponse(EntityUtils.toString(response.getEntity()));
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("Error while getting mapping for indexName : " + index.getIndexName(), e);
|
||||
}
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
|
||||
|
||||
return restTemplate.execute(client -> {
|
||||
GetMappingsResponse mapping = client.indices().getMapping(mappingsRequest, RequestOptions.DEFAULT);
|
||||
// we only return data for the first index name that was requested (always have done so)
|
||||
String index1 = mappingsRequest.indices()[0];
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
return mapping.mappings().get(index1).getSourceAsMap();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAlias(AliasQuery query, IndexCoordinates index) {
|
||||
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
|
||||
|
||||
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
|
||||
try {
|
||||
return client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to update aliases with request: " + request, e);
|
||||
}
|
||||
return restTemplate
|
||||
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAlias(AliasQuery query, IndexCoordinates index) {
|
||||
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);
|
||||
try {
|
||||
return client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException(
|
||||
"failed to update aliases with indicesRemoveAliasesRequest: " + indicesAliasesRequest, e);
|
||||
}
|
||||
return restTemplate.execute(
|
||||
client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AliasMetaData> queryForAlias(String indexName) {
|
||||
List<AliasMetaData> aliases = null;
|
||||
RestClient restClient = client.getLowLevelClient();
|
||||
Response response;
|
||||
String aliasResponse;
|
||||
protected List<AliasMetaData> doQueryForAlias(IndexCoordinates index) {
|
||||
|
||||
try {
|
||||
response = restClient.performRequest(new Request("GET", '/' + indexName + "/_alias/*"));
|
||||
aliasResponse = EntityUtils.toString(response.getEntity());
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchException("Error while getting mapping for indexName : " + indexName, e);
|
||||
}
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
|
||||
|
||||
return convertAliasResponse(aliasResponse);
|
||||
return restTemplate.execute(client -> {
|
||||
GetAliasesResponse alias = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);
|
||||
// we only return data for the first index name that was requested (always have done so)
|
||||
String index1 = getAliasesRequest.indices()[0];
|
||||
return new ArrayList<>(alias.getAliases().get(index1));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(String indexName) {
|
||||
return getSettings(indexName, false);
|
||||
protected Map<String, Object> doGetSettings(IndexCoordinates index, boolean includeDefaults) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
GetSettingsRequest getSettingsRequest = requestFactory.getSettingsRequest(index, includeDefaults);
|
||||
GetSettingsResponse response = restTemplate.execute(client -> client.indices() //
|
||||
.getSettings(getSettingsRequest, RequestOptions.DEFAULT));
|
||||
|
||||
return convertSettingsResponseToMap(response, getSettingsRequest.indices()[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(String indexName, boolean includeDefaults) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for getSettings");
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest() //
|
||||
.indices(indexName) //
|
||||
.includeDefaults(includeDefaults);
|
||||
|
||||
try {
|
||||
GetSettingsResponse response = client.indices() //
|
||||
.getSettings(request, RequestOptions.DEFAULT);
|
||||
|
||||
return convertSettingsResponseToMap(response, indexName);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to get settings for index: " + indexName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(IndexCoordinates index) {
|
||||
protected void doRefresh(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "No index defined for refresh()");
|
||||
|
||||
try {
|
||||
client.indices().refresh(refreshRequest(index.getIndexNames()), RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to refresh index: " + index, e);
|
||||
}
|
||||
RefreshRequest refreshRequest = requestFactory.refreshRequest(index);
|
||||
restTemplate.execute(client -> client.indices().refresh(refreshRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
// region Helper methods
|
||||
private Map<String, Object> convertMappingResponse(String mappingResponse) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
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 ElasticsearchException("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 ElasticsearchException("Could not map alias response : " + aliasResponse, e);
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.client.Requests.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||
import org.springframework.data.elasticsearch.annotations.Setting;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class);
|
||||
|
||||
@Nullable private final Class<?> boundClass;
|
||||
private final IndexCoordinates boundIndex;
|
||||
private final RequestFactory requestFactory;
|
||||
private final ReactiveElasticsearchOperations operations;
|
||||
private final ElasticsearchConverter converter;
|
||||
|
||||
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(operations, "operations must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
this.operations = operations;
|
||||
this.converter = operations.getElasticsearchConverter();
|
||||
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
|
||||
this.boundClass = null;
|
||||
this.boundIndex = index;
|
||||
}
|
||||
|
||||
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class<?> clazz) {
|
||||
|
||||
Assert.notNull(operations, "operations must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
this.operations = operations;
|
||||
this.converter = operations.getElasticsearchConverter();
|
||||
this.requestFactory = new RequestFactory(operations.getElasticsearchConverter());
|
||||
this.boundClass = clazz;
|
||||
this.boundIndex = getIndexCoordinatesFor(clazz);
|
||||
}
|
||||
|
||||
// region index management
|
||||
@Override
|
||||
public Mono<Boolean> create() {
|
||||
|
||||
String indexName = getIndexCoordinates().getIndexName();
|
||||
Document settings = null;
|
||||
|
||||
if (boundClass != null) {
|
||||
Class<?> clazz = boundClass;
|
||||
|
||||
if (clazz.isAnnotationPresent(Setting.class)) {
|
||||
String settingPath = clazz.getAnnotation(Setting.class).settingPath();
|
||||
|
||||
return loadDocument(settingPath, "@Setting").flatMap(document -> doCreate(indexName, document));
|
||||
}
|
||||
|
||||
settings = getRequiredPersistentEntity(clazz).getDefaultSettings();
|
||||
}
|
||||
|
||||
return doCreate(indexName, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> create(Document settings) {
|
||||
return doCreate(getIndexCoordinates().getIndexName(), settings);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doCreate(String indexName, @Nullable Document settings) {
|
||||
CreateIndexRequest request = requestFactory.createIndexRequestReactive(getIndexCoordinates().getIndexName(),
|
||||
settings);
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> delete() {
|
||||
|
||||
return exists() //
|
||||
.flatMap(exists -> {
|
||||
|
||||
if (exists) {
|
||||
DeleteIndexRequest request = requestFactory.deleteIndexRequest(getIndexCoordinates());
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.deleteIndex(request)))
|
||||
.onErrorResume(NoSuchIndexException.class, e -> Mono.just(false));
|
||||
} else {
|
||||
return Mono.just(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists() {
|
||||
|
||||
GetIndexRequest request = requestFactory.getIndexRequestReactive(getIndexCoordinates().getIndexName());
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.existsIndex(request)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> refresh() {
|
||||
return Mono.from(operations.executeWithIndicesClient(
|
||||
client -> client.refreshIndex(refreshRequest(getIndexCoordinates().getIndexNames()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> createMapping() {
|
||||
return createMapping(checkForBoundClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> createMapping(Class<?> clazz) {
|
||||
|
||||
if (clazz.isAnnotationPresent(Mapping.class)) {
|
||||
String mappingPath = clazz.getAnnotation(Mapping.class).mappingPath();
|
||||
return loadDocument(mappingPath, "@Mapping");
|
||||
}
|
||||
|
||||
String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz);
|
||||
return Mono.just(Document.parse(mapping));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> putMapping(Mono<Document> mapping) {
|
||||
return mapping.map(document -> requestFactory.putMappingRequestReactive(getIndexCoordinates(), document)) //
|
||||
.flatMap(request -> Mono.from(operations.executeWithIndicesClient(client -> client.putMapping(request))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> getMapping() {
|
||||
|
||||
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||
GetMappingsRequest request = requestFactory.getMappingRequestReactive(indexCoordinates);
|
||||
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getMapping(request)))
|
||||
.flatMap(getMappingsResponse -> {
|
||||
Document document = Document.create();
|
||||
document.put("properties",
|
||||
getMappingsResponse.mappings().get(indexCoordinates.getIndexName()).get("properties").getSourceAsMap());
|
||||
return Mono.just(document);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Document> getSettings(boolean includeDefaults) {
|
||||
|
||||
String indexName = getIndexCoordinates().getIndexName();
|
||||
GetSettingsRequest request = requestFactory.getSettingsRequest(indexName, includeDefaults);
|
||||
|
||||
return Mono.from(operations.executeWithIndicesClient(client -> client.getSettings(request)))
|
||||
.map(getSettingsResponse -> requestFactory.fromSettingsResponse(getSettingsResponse, indexName));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
/**
|
||||
* get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed
|
||||
* index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned.
|
||||
*
|
||||
* @return IndexCoordinates
|
||||
*/
|
||||
private IndexCoordinates getIndexCoordinates() {
|
||||
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex;
|
||||
}
|
||||
|
||||
private IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
return operations.getElasticsearchConverter().getMappingContext().getRequiredPersistentEntity(clazz)
|
||||
.getIndexCoordinates();
|
||||
}
|
||||
|
||||
private ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
|
||||
return converter.getMappingContext().getRequiredPersistentEntity(clazz);
|
||||
}
|
||||
|
||||
private Mono<Document> loadDocument(String path, String annotation) {
|
||||
|
||||
if (hasText(path)) {
|
||||
return ReactiveResourceUtil.readFileFromClasspath(path).flatMap(s -> {
|
||||
if (hasText(s)) {
|
||||
return Mono.just(Document.parse(s));
|
||||
} else {
|
||||
return Mono.just(Document.create());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOGGER.info("path in {} has to be defined. Using default instead.", annotation);
|
||||
}
|
||||
|
||||
return Mono.just(Document.create());
|
||||
}
|
||||
|
||||
private Class<?> checkForBoundClass() {
|
||||
if (boundClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||
}
|
||||
return boundClass;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
+51
-44
@@ -21,19 +21,23 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder;
|
||||
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
|
||||
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
|
||||
import org.elasticsearch.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;
|
||||
|
||||
/**
|
||||
@@ -47,36 +51,46 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
|
||||
private final Client client;
|
||||
|
||||
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter) {
|
||||
super(elasticsearchConverter);
|
||||
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
|
||||
public boolean createIndex(String indexName, Object settings) {
|
||||
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, indexName,
|
||||
protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) {
|
||||
CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, index,
|
||||
settings);
|
||||
return createIndexRequestBuilder.execute().actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteIndex(String indexName) {
|
||||
protected boolean doDelete(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for delete operation");
|
||||
Assert.notNull(index, "No index defined for delete operation");
|
||||
|
||||
if (indexExists(indexName)) {
|
||||
return client.admin().indices().delete(new DeleteIndexRequest(indexName)).actionGet().isAcknowledged();
|
||||
if (doExists(index)) {
|
||||
DeleteIndexRequest deleteIndexRequest = requestFactory.deleteIndexRequest(index);
|
||||
return client.admin().indices().delete(deleteIndexRequest).actionGet().isAcknowledged();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean indexExists(String indexName) {
|
||||
return client.admin().indices().exists(indicesExistsRequest(indexName)).actionGet().isExists();
|
||||
protected boolean doExists(IndexCoordinates index) {
|
||||
|
||||
IndicesExistsRequest indicesExistsRequest = requestFactory.indicesExistsRequest(index);
|
||||
return client.admin().indices().exists(indicesExistsRequest).actionGet().isExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putMapping(IndexCoordinates index, Object mapping) {
|
||||
protected boolean doPutMapping(IndexCoordinates index, Document mapping) {
|
||||
|
||||
Assert.notNull(index, "No index defined for putMapping()");
|
||||
|
||||
@@ -85,70 +99,63 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMapping(IndexCoordinates index) {
|
||||
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);
|
||||
}
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(client, index);
|
||||
|
||||
return client.admin().indices().getMappings( //
|
||||
mappingsRequest).actionGet() //
|
||||
.getMappings().get(mappingsRequest.indices()[0]).get(IndexCoordinates.TYPE) //
|
||||
.getSourceAsMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAlias(AliasQuery query, IndexCoordinates index) {
|
||||
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
|
||||
public boolean removeAlias(AliasQuery query, IndexCoordinates index) {
|
||||
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();
|
||||
IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory
|
||||
.indicesRemoveAliasesRequestBuilder(client, query, index);
|
||||
return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AliasMetaData> queryForAlias(String indexName) {
|
||||
return client.admin().indices().getAliases(new GetAliasesRequest().indices(indexName)).actionGet().getAliases()
|
||||
.get(indexName);
|
||||
protected List<AliasMetaData> doQueryForAlias(IndexCoordinates index) {
|
||||
|
||||
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
|
||||
return client.admin().indices().getAliases(getAliasesRequest).actionGet().getAliases().get(index.getIndexName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(String indexName) {
|
||||
return getSettings(indexName, false);
|
||||
}
|
||||
protected Map<String, Object> doGetSettings(IndexCoordinates index, boolean includeDefaults) {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings(String indexName, boolean includeDefaults) {
|
||||
|
||||
Assert.notNull(indexName, "No index defined for getSettings");
|
||||
|
||||
GetSettingsRequest request = new GetSettingsRequest() //
|
||||
.indices(indexName) //
|
||||
.includeDefaults(includeDefaults);
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
GetSettingsRequest getSettingsRequest = requestFactory.getSettingsRequest(index, includeDefaults);
|
||||
GetSettingsResponse response = client.admin() //
|
||||
.indices() //
|
||||
.getSettings(request) //
|
||||
.getSettings(getSettingsRequest) //
|
||||
.actionGet();
|
||||
|
||||
return convertSettingsResponseToMap(response, indexName);
|
||||
return requestFactory.fromSettingsResponse(response, index.getIndexName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(IndexCoordinates index) {
|
||||
protected void doRefresh(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "No index defined for refresh()");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
client.admin().indices().refresh(refreshRequest(index.getIndexNames())).actionGet();
|
||||
RefreshRequest request = requestFactory.refreshRequest(index);
|
||||
client.admin().indices().refresh(request).actionGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+154
-19
@@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
|
||||
@@ -25,6 +24,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -68,9 +68,9 @@ public interface DocumentOperations {
|
||||
* saves the given entities to the given index
|
||||
*
|
||||
* @param entities must not be {@literal null}
|
||||
* @param index the idnex to save the entities in, 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 entites
|
||||
* @return the saved entities
|
||||
*/
|
||||
<T> Iterable<T> save(Iterable<T> entities, IndexCoordinates index);
|
||||
|
||||
@@ -87,21 +87,42 @@ public interface DocumentOperations {
|
||||
* Index an object. Will do save or update.
|
||||
*
|
||||
* @param query the query defining the object
|
||||
* @param index the index from which the object is read.
|
||||
* @param index the index where the object is stored.
|
||||
* @return returns the document id
|
||||
*/
|
||||
String index(IndexQuery query, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Retrieves an object from an index.
|
||||
* Retrieves an object from the index specified in the entity's Document annotation.
|
||||
*
|
||||
* @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
|
||||
* @param id the id of the object
|
||||
* @param clazz the entity class,
|
||||
* @param <T> the entity type
|
||||
* @return the entity
|
||||
*/
|
||||
@Nullable
|
||||
<T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index);
|
||||
<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
|
||||
* @return list of objects, contains null values for ids that are not found
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> List<T> multiGet(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
@@ -109,10 +130,40 @@ public interface DocumentOperations {
|
||||
* @param query the query defining the ids of the objects to get
|
||||
* @param clazz the type of the object to be returned
|
||||
* @param index the index(es) from which the objects are read.
|
||||
* @return list of objects
|
||||
* @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
|
||||
* @param clazz the entity class
|
||||
* @return the ids of the indexed objects
|
||||
* @since 4.1
|
||||
*/
|
||||
default List<String> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
|
||||
return bulkIndex(queries, BulkOptions.defaultOptions(), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk index all objects. Will do save or update.
|
||||
*
|
||||
@@ -123,6 +174,17 @@ public interface DocumentOperations {
|
||||
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
|
||||
* @param clazz the entity class
|
||||
* @return the ids of the indexed objects
|
||||
* @since 4.1
|
||||
*/
|
||||
List<String> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Bulk index all objects. Will do save or update.
|
||||
*
|
||||
@@ -141,6 +203,14 @@ public interface DocumentOperations {
|
||||
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update.
|
||||
* @param clazz the entity class
|
||||
* @param queries the queries to execute in bulk
|
||||
* @since 4.1
|
||||
*/
|
||||
void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Bulk update all objects. Will do update.
|
||||
*
|
||||
@@ -158,6 +228,42 @@ public interface DocumentOperations {
|
||||
*/
|
||||
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}
|
||||
* @since 4.1
|
||||
*/
|
||||
void delete(Query query, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Delete all records matching the query.
|
||||
*
|
||||
@@ -168,14 +274,6 @@ public interface DocumentOperations {
|
||||
*/
|
||||
void delete(Query query, Class<?> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete all records matching the query.
|
||||
*
|
||||
* @param query query defining the objects
|
||||
* @param index the index where to delete the records
|
||||
*/
|
||||
void delete(DeleteQuery query, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Partial update of the document.
|
||||
*
|
||||
@@ -184,4 +282,41 @@ public interface DocumentOperations {
|
||||
* @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
|
||||
}
|
||||
|
||||
+56
-5
@@ -13,24 +13,36 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.io.IOException;
|
||||
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 {
|
||||
@@ -38,6 +50,10 @@ 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;
|
||||
@@ -46,22 +62,57 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
|
||||
ex);
|
||||
}
|
||||
|
||||
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex.getCause() instanceof ConnectException) {
|
||||
if (ex instanceof ValidationException) {
|
||||
return new DataIntegrityViolationException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause instanceof IOException) {
|
||||
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(((ElasticsearchStatusException) ex).getIndex()));
|
||||
|
||||
if (ex.getCause() instanceof ElasticsearchException) {
|
||||
return indexAvailable((ElasticsearchException) ex.getCause());
|
||||
}
|
||||
|
||||
if (ex instanceof ElasticsearchStatusException) {
|
||||
return StringUtils.hasText(ObjectUtils.nullSafeToString(ex.getIndex()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return !CollectionUtils.contains(metadata.iterator(), "_na_");
|
||||
}
|
||||
|
||||
+123
-45
@@ -21,6 +21,7 @@ import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -40,7 +41,19 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations {
|
||||
|
||||
IndexOperations getIndexOperations();
|
||||
/**
|
||||
* get an {@link IndexOperations} that is bound to the given class
|
||||
*
|
||||
* @return IndexOperations
|
||||
*/
|
||||
IndexOperations indexOps(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* get an {@link IndexOperations} that is bound to the given index
|
||||
*
|
||||
* @return IndexOperations
|
||||
*/
|
||||
IndexOperations indexOps(IndexCoordinates index);
|
||||
|
||||
ElasticsearchConverter getElasticsearchConverter();
|
||||
|
||||
@@ -48,15 +61,15 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
|
||||
// region IndexOperations
|
||||
/**
|
||||
* Create an index for given indexName if it does not already exist.
|
||||
* 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#createIndex(String) instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(String indexName) {
|
||||
return getIndexOperations().createIndex(indexName);
|
||||
return indexOps(IndexCoordinates.of(indexName)).create();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,11 +78,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param indexName the name of the index
|
||||
* @param settings the index settings
|
||||
* @return {@literal true} if the index was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#createIndex(String, Object)} instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(String indexName, Object settings) {
|
||||
return getIndexOperations().createIndex(indexName, settings);
|
||||
return indexOps(IndexCoordinates.of(indexName)).create(getDocument(settings));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,11 +91,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#createIndex(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(Class<?> clazz) {
|
||||
return getIndexOperations().createIndex(clazz);
|
||||
return indexOps(clazz).create();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,11 +105,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param settings the index settings
|
||||
* @return {@literal true} if the index was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#createIndex(Class, Object)} instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#create(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean createIndex(Class<?> clazz, Object settings) {
|
||||
return getIndexOperations().createIndex(clazz, settings);
|
||||
return indexOps(clazz).create(getDocument(settings));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,11 +118,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index was deleted
|
||||
* @deprecated since 4.0, use {@link IndexOperations#deleteIndex(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#delete()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean deleteIndex(Class<?> clazz) {
|
||||
return getIndexOperations().deleteIndex(clazz);
|
||||
return indexOps(clazz).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,11 +130,23 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param indexName the name of the index to delete
|
||||
* @return {@literal true} if the index was deleted
|
||||
* @deprecated since 4.0, use {@link IndexOperations#deleteIndex(String)} instead}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#delete()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean deleteIndex(String indexName) {
|
||||
return getIndexOperations().deleteIndex(indexName);
|
||||
return indexOps(IndexCoordinates.of(indexName)).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an index for an IndexCoordinate
|
||||
*
|
||||
* @param index the index to delete
|
||||
* @return {@literal true} if the index was deleted
|
||||
* @deprecated since 4.0 use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#delete()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean deleteIndex(IndexCoordinates index) {
|
||||
return indexOps(index).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,11 +154,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index exists
|
||||
* @deprecated since 4.0, use {@link IndexOperations#indexExists(String)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#exists()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean indexExists(String indexName) {
|
||||
return getIndexOperations().indexExists(indexName);
|
||||
return indexOps(IndexCoordinates.of(indexName)).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,11 +167,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index exists
|
||||
* @deprecated since 4.0, use {@link IndexOperations#indexExists(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)} and {@link IndexOperations#exists()}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean indexExists(Class<?> clazz) {
|
||||
return getIndexOperations().indexExists(clazz);
|
||||
return indexOps(clazz).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,11 +180,13 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @deprecated since 4.0, use {@link IndexOperations#putMapping(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)}, {@link IndexOperations#createMapping(Class)} and
|
||||
* {@link IndexOperations#putMapping(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean putMapping(Class<?> clazz) {
|
||||
return getIndexOperations().putMapping(clazz);
|
||||
IndexOperations indexOps = indexOps(clazz);
|
||||
return indexOps.putMapping(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,11 +196,13 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @deprecated since 4.0, use {@link IndexOperations#putMapping(IndexCoordinates, Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)}, {@link IndexOperations#createMapping(Class)} and
|
||||
* {@link IndexOperations#putMapping(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean putMapping(IndexCoordinates index, Class<?> clazz) {
|
||||
return getIndexOperations().putMapping(index, clazz);
|
||||
IndexOperations indexOps = indexOps(index);
|
||||
return indexOps.putMapping(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,11 +211,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param index the index to store the mapping to
|
||||
* @param mappings can be a JSON String or a {@link Map}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @deprecated since 4.0, use {@link IndexOperations#putMapping(IndexCoordinates, Object)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#putMapping(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean putMapping(IndexCoordinates index, Object mappings) {
|
||||
return getIndexOperations().putMapping(index, mappings);
|
||||
return indexOps(index).putMapping(getDocument(mappings));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,11 +225,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param mappings can be a JSON String or a {@link Map}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @deprecated since 4.0, use {@link IndexOperations#putMapping(Class, Object)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)} and {@link IndexOperations#putMapping(Document)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean putMapping(Class<?> clazz, Object mappings) {
|
||||
return getIndexOperations().putMapping(clazz, mappings);
|
||||
return indexOps(clazz).putMapping(getDocument(mappings));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,11 +238,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}.
|
||||
* @return the mapping
|
||||
* @deprecated since 4.0, use {@link IndexOperations#getMapping(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)} and {@link IndexOperations#getMapping()}
|
||||
*/
|
||||
@Deprecated
|
||||
default Map<String, Object> getMapping(Class<?> clazz) {
|
||||
return getIndexOperations().getMapping(clazz);
|
||||
return indexOps(clazz).getMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,11 +250,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param index the index to read the mapping from
|
||||
* @return the mapping
|
||||
* @deprecated since 4.0, use {@link IndexOperations#getMapping(IndexCoordinates)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#getMapping()}
|
||||
*/
|
||||
@Deprecated
|
||||
default Map<String, Object> getMapping(IndexCoordinates index) {
|
||||
return getIndexOperations().getMapping(index);
|
||||
return indexOps(index).getMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,11 +263,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param query query defining the alias
|
||||
* @param index the index for which to add an alias
|
||||
* @return true if the alias was created
|
||||
* @deprecated since 4.0, use {@link IndexOperations#addAlias(AliasQuery, IndexCoordinates)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#addAlias(AliasQuery)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean addAlias(AliasQuery query, IndexCoordinates index) {
|
||||
return getIndexOperations().addAlias(query, index);
|
||||
return indexOps(index).addAlias(query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,11 +276,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param query query defining the alias
|
||||
* @param index the index for which to remove an alias
|
||||
* @return true if the alias was removed
|
||||
* @deprecated since 4.0, use {@link IndexOperations#removeAlias(AliasQuery, IndexCoordinates)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} {@link IndexOperations#removeAlias(AliasQuery)}
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean removeAlias(AliasQuery query, IndexCoordinates index) {
|
||||
return getIndexOperations().removeAlias(query, index);
|
||||
return indexOps(index).removeAlias(query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,11 +288,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return alias information
|
||||
* @deprecated since 4.0, use {@link IndexOperations#queryForAlias(String)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#queryForAlias()}
|
||||
*/
|
||||
@Deprecated
|
||||
default List<AliasMetaData> queryForAlias(String indexName) {
|
||||
return getIndexOperations().queryForAlias(indexName);
|
||||
return indexOps(IndexCoordinates.of(indexName)).queryForAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,11 +300,11 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return the settings
|
||||
* @deprecated since 4.0, use {@link IndexOperations#getSettings(String)} )} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#getSettings()} )}
|
||||
*/
|
||||
@Deprecated
|
||||
default Map<String, Object> getSetting(String indexName) {
|
||||
return getIndexOperations().getSettings(indexName);
|
||||
return indexOps(IndexCoordinates.of(indexName)).getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,22 +313,48 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return the settings
|
||||
* @deprecated since 4.0, use {@link IndexOperations#getSettings(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)} and {@link IndexOperations#getSettings()}
|
||||
*/
|
||||
@Deprecated
|
||||
default Map<String, Object> getSetting(Class<?> clazz) {
|
||||
return getIndexOperations().getSettings(clazz);
|
||||
return indexOps(clazz).getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for a given indexName.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @deprecated since 4.0 use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#getSettings(boolean)} ()}
|
||||
* @return the settings
|
||||
*/
|
||||
@Deprecated
|
||||
default Map<String, Object> getSettings(String indexName, boolean includeDefaults) {
|
||||
return indexOps(IndexCoordinates.of(indexName)).getSettings(includeDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for a given class.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return the settings
|
||||
* @deprecated since 4.0 use {@link #indexOps(Class)} and {@link IndexOperations#getSettings(boolean)} ()}
|
||||
*/
|
||||
default Map<String, Object> getSettings(Class<?> clazz, boolean includeDefaults) {
|
||||
return indexOps(clazz).getSettings(includeDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the index(es).
|
||||
*
|
||||
* @param index the index to refresh
|
||||
* @deprecated since 4.0, use {@link IndexOperations#refresh(IndexCoordinates)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(IndexCoordinates)} and {@link IndexOperations#refresh()} instead}
|
||||
*/
|
||||
@Deprecated
|
||||
default void refresh(IndexCoordinates index) {
|
||||
getIndexOperations().refresh(index);
|
||||
indexOps(index).refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,13 +362,36 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @deprecated since 4.0, use {@link IndexOperations#refresh(Class)} instead}
|
||||
* @deprecated since 4.0, use {@link #indexOps(Class)} and {@link IndexOperations#refresh()} instead}
|
||||
*/
|
||||
@Deprecated
|
||||
default void refresh(Class<?> clazz) {
|
||||
getIndexOperations().refresh(clazz);
|
||||
indexOps(clazz).refresh();
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* converts an object to a Document
|
||||
*
|
||||
* @param object
|
||||
* @return
|
||||
* @deprecated since 4.0, helper method for deprecated functions
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
default Document getDocument(Object object) {
|
||||
Document document = null;
|
||||
|
||||
try {
|
||||
if (object instanceof String) {
|
||||
document = Document.parse((String) object);
|
||||
} else if (object instanceof Map) {
|
||||
document = Document.from((Map<String, Object>) object);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("object cannot be converted to Document", e);
|
||||
}
|
||||
return document;
|
||||
} // endregion
|
||||
|
||||
// region helper
|
||||
/**
|
||||
|
||||
+181
-108
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.action.bulk.BulkRequest;
|
||||
import org.elasticsearch.action.delete.DeleteRequest;
|
||||
@@ -32,25 +33,24 @@ import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.search.SearchScrollRequest;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
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.data.elasticsearch.support.SearchHitsUtil;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -89,53 +89,80 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
|
||||
|
||||
private RestHighLevelClient client;
|
||||
private ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
|
||||
// region Initialization
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
||||
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
initialize(client, createElasticsearchConverter());
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
initialize(createElasticsearchConverter());
|
||||
}
|
||||
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
initialize(client, elasticsearchConverter);
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
initialize(elasticsearchConverter);
|
||||
}
|
||||
|
||||
private void initialize(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
this.client = client;
|
||||
initialize(elasticsearchConverter, new DefaultIndexOperations(client, elasticsearchConverter));
|
||||
// endregion
|
||||
|
||||
// region IndexOperations
|
||||
@Override
|
||||
public IndexOperations indexOps(Class<?> clazz) {
|
||||
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
return new DefaultIndexOperations(this, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexOperations indexOps(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return new DefaultIndexOperations(this, index);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
IndexRequest request = requestFactory.indexRequest(query, index);
|
||||
try {
|
||||
String documentId = client.index(request, RequestOptions.DEFAULT).getId();
|
||||
|
||||
// We should call this because we are not going through a mapper.
|
||||
if (query.getObject() != null) {
|
||||
setPersistentEntityId(query.getObject(), documentId);
|
||||
}
|
||||
return documentId;
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while index for request: " + request.toString(), e);
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
|
||||
IndexRequest request = requestFactory.indexRequest(query, index);
|
||||
String documentId = execute(client -> client.index(request, RequestOptions.DEFAULT).getId());
|
||||
|
||||
// We should call this because we are not going through a mapper.
|
||||
Object queryObject = query.getObject();
|
||||
if (queryObject != null) {
|
||||
setPersistentEntityId(queryObject, documentId);
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequest request = requestFactory.getRequest(query, index);
|
||||
try {
|
||||
GetResponse response = client.get(request, RequestOptions.DEFAULT);
|
||||
return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while getting for request: " + request.toString(), e);
|
||||
}
|
||||
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequest request = requestFactory.getRequest(id, index);
|
||||
GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT));
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
return callback.doWith(DocumentAdapters.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,12 +172,17 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notEmpty(query.getIds(), "No Id define for Query");
|
||||
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, index);
|
||||
try {
|
||||
MultiGetResponse result = client.mget(request, RequestOptions.DEFAULT);
|
||||
return elasticsearchConverter.mapDocuments(DocumentAdapters.from(result), clazz);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while multiget for request: " + request.toString(), e);
|
||||
}
|
||||
MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT));
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
return DocumentAdapters.from(result).stream().map(callback::doWith).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String id, IndexCoordinates index) {
|
||||
GetRequest request = requestFactory.getRequest(id, index);
|
||||
request.fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE);
|
||||
return execute(client -> client.get(request, RequestOptions.DEFAULT).isExists());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,41 +205,42 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
DeleteRequest request = new DeleteRequest(index.getIndexName(), id);
|
||||
try {
|
||||
return client.delete(request, RequestOptions.DEFAULT).getId();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while deleting item request: " + request.toString(), e);
|
||||
}
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
DeleteRequest request = requestFactory.deleteRequest(elasticsearchConverter.convertId(id), index);
|
||||
return execute(client -> client.delete(request, RequestOptions.DEFAULT).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(query, clazz, index);
|
||||
execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void delete(DeleteQuery deleteQuery, IndexCoordinates index) {
|
||||
DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(deleteQuery, index);
|
||||
try {
|
||||
client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for delete request: " + deleteByQueryRequest.toString(), e);
|
||||
}
|
||||
execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResponse update(UpdateQuery query, IndexCoordinates index) {
|
||||
UpdateRequest request = requestFactory.updateRequest(query, index);
|
||||
try {
|
||||
return client.update(request, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while update for request: " + request.toString(), e);
|
||||
}
|
||||
UpdateResponse.Result result = UpdateResponse.Result
|
||||
.valueOf(execute(client -> client.update(request, RequestOptions.DEFAULT)).getResult().name());
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<String> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
BulkRequest bulkRequest = requestFactory.bulkRequest(queries, bulkOptions, index);
|
||||
try {
|
||||
return checkForBulkOperationFailure(client.bulk(bulkRequest, RequestOptions.DEFAULT));
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e);
|
||||
}
|
||||
List<String> ids = checkForBulkOperationFailure(
|
||||
execute(client -> client.bulk(bulkRequest, RequestOptions.DEFAULT)));
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
return ids;
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -225,93 +258,133 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
searchRequest.source().size(0);
|
||||
|
||||
try {
|
||||
return SearchHitsUtil.getTotalCount(client.search(searchRequest, RequestOptions.DEFAULT).getHits());
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request: " + searchRequest.toString(), e);
|
||||
}
|
||||
return SearchHitsUtil
|
||||
.getTotalCount(execute(client -> client.search(searchRequest, RequestOptions.DEFAULT).getHits()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
|
||||
SearchResponse response;
|
||||
try {
|
||||
response = client.search(searchRequest, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request: " + searchRequest.toString(), e);
|
||||
}
|
||||
return elasticsearchConverter.read(clazz, SearchDocumentResponse.from(response));
|
||||
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
|
||||
|
||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScrolledPage<SearchHit<T>> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query.getPageable(), "Query.pageable is required for scan & scroll");
|
||||
Assert.notNull(query.getPageable(), "pageable of query must not be null.");
|
||||
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
|
||||
searchRequest.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
||||
|
||||
try {
|
||||
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
|
||||
return elasticsearchConverter.mapResults(SearchDocumentResponse.from(result), clazz, null);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request with scroll: " + searchRequest.toString(), e);
|
||||
}
|
||||
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
|
||||
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||
index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScrolledPage<SearchHit<T>> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz) {
|
||||
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
SearchScrollRequest request = new SearchScrollRequest(scrollId);
|
||||
request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
||||
SearchResponse response;
|
||||
try {
|
||||
response = client.searchScroll(request, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request with scroll: " + request.toString(), e);
|
||||
}
|
||||
return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, Pageable.unpaged());
|
||||
|
||||
SearchResponse response = execute(client -> client.scroll(request, RequestOptions.DEFAULT));
|
||||
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = //
|
||||
new ReadSearchScrollDocumentResponseCallback<>(clazz, index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchScrollClear(String scrollId) {
|
||||
ClearScrollRequest request = new ClearScrollRequest();
|
||||
request.addScrollId(scrollId);
|
||||
public void searchScrollClear(List<String> scrollIds) {
|
||||
try {
|
||||
client.clearScroll(request, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request with scroll: " + request.toString(), e);
|
||||
ClearScrollRequest request = new ClearScrollRequest();
|
||||
request.scrollIds(scrollIds);
|
||||
execute(client -> client.clearScroll(request, RequestOptions.DEFAULT));
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not clear scroll: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
SearchRequest searchRequest = new SearchRequest(index.getIndexNames());
|
||||
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
|
||||
sourceBuilder.suggest(suggestion);
|
||||
searchRequest.source(sourceBuilder);
|
||||
|
||||
try {
|
||||
return client.search(searchRequest, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Could not execute search request : " + searchRequest.toString(), e);
|
||||
}
|
||||
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(suggestion, index);
|
||||
return execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request) {
|
||||
MultiSearchResponse response;
|
||||
try {
|
||||
response = client.multiSearch(request, RequestOptions.DEFAULT);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Error for search request: " + request.toString(), e);
|
||||
}
|
||||
MultiSearchResponse response = execute(client -> client.multiSearch(request, RequestOptions.DEFAULT));
|
||||
MultiSearchResponse.Item[] items = response.getResponses();
|
||||
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
|
||||
return items;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ClientCallback
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link RestHighLevelClient}.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ClientCallback<T> {
|
||||
T doWithClient(RestHighLevelClient client) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback with the {@link RestHighLevelClient}
|
||||
*
|
||||
* @param callback the callback to execute, must not be {@literal null}
|
||||
* @param <T> the type returned from the callback
|
||||
* @return the callback result
|
||||
* @since 4.0
|
||||
*/
|
||||
public <T> T execute(ClientCallback<T> callback) {
|
||||
|
||||
Assert.notNull(callback, "callback must not be null");
|
||||
|
||||
try {
|
||||
return callback.doWithClient(client);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
throw translateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
|
||||
* RuntimeException
|
||||
*
|
||||
* @param exception the Exception to map
|
||||
* @return the potentially translated RuntimeException.
|
||||
* @since 4.0
|
||||
*/
|
||||
private RuntimeException translateException(Exception exception) {
|
||||
|
||||
RuntimeException runtimeException = exception instanceof RuntimeException ? (RuntimeException) exception
|
||||
: new RuntimeException(exception.getMessage(), exception);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator
|
||||
.translateExceptionIfPossible(runtimeException);
|
||||
|
||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region helper methods
|
||||
@Override
|
||||
protected String getClusterVersion() {
|
||||
try {
|
||||
return execute(client -> client.info(RequestOptions.DEFAULT)).getVersion().getNumber();
|
||||
} catch (Exception ignored) {}
|
||||
return null;
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
+168
-31
@@ -16,35 +16,40 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.action.ActionFuture;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||
import org.elasticsearch.action.delete.DeleteRequestBuilder;
|
||||
import org.elasticsearch.action.get.GetRequestBuilder;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.action.get.MultiGetRequestBuilder;
|
||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.search.MultiSearchRequest;
|
||||
import org.elasticsearch.action.search.MultiSearchResponse;
|
||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.update.UpdateRequestBuilder;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.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.data.elasticsearch.support.SearchHitsUtil;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -75,16 +80,20 @@ import org.springframework.util.Assert;
|
||||
* @author Martin Choraine
|
||||
* @author Farid Azaza
|
||||
* @author Gyula Attila Csorogi
|
||||
* @author Roman Puchkovskiy
|
||||
* @deprecated as of 4.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
private static final Logger QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class);
|
||||
|
||||
private Client client;
|
||||
@Nullable private String searchTimeout;
|
||||
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
|
||||
// region Initialization
|
||||
public ElasticsearchTemplate(Client client) {
|
||||
this.client = client;
|
||||
@@ -97,9 +106,29 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
private void initialize(Client client, ElasticsearchConverter elasticsearchConverter) {
|
||||
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
initialize(elasticsearchConverter, new DefaultTransportIndexOperations(client, elasticsearchConverter));
|
||||
initialize(elasticsearchConverter);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region IndexOperations
|
||||
@Override
|
||||
public IndexOperations indexOps(Class<?> clazz) {
|
||||
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
return new DefaultTransportIndexOperations(client, elasticsearchConverter, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexOperations indexOps(IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return new DefaultTransportIndexOperations(client, elasticsearchConverter, index);
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -117,32 +146,58 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
|
||||
IndexRequestBuilder indexRequestBuilder = requestFactory.indexRequestBuilder(client, query, index);
|
||||
String documentId = indexRequestBuilder.execute().actionGet().getId();
|
||||
// We should call this because we are not going through a mapper.
|
||||
if (query.getObject() != null) {
|
||||
setPersistentEntityId(query.getObject(), documentId);
|
||||
ActionFuture<IndexResponse> future = indexRequestBuilder.execute();
|
||||
IndexResponse response;
|
||||
try {
|
||||
response = future.actionGet();
|
||||
} catch (RuntimeException e) {
|
||||
throw translateException(e);
|
||||
}
|
||||
String documentId = response.getId();
|
||||
|
||||
// We should call this because we are not going through a mapper.
|
||||
Object queryObject = query.getObject();
|
||||
if (queryObject != null) {
|
||||
setPersistentEntityId(queryObject, documentId);
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(GetQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, query, index);
|
||||
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index);
|
||||
GetResponse response = getRequestBuilder.execute().actionGet();
|
||||
return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz);
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
return callback.doWith(DocumentAdapters.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.notEmpty(query.getIds(), "No Id define for Query");
|
||||
Assert.notEmpty(query.getIds(), "No Ids defined for Query");
|
||||
|
||||
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, index);
|
||||
|
||||
return elasticsearchConverter.mapDocuments(DocumentAdapters.from(builder.execute().actionGet()), clazz);
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
List<Document> documents = DocumentAdapters.from(builder.execute().actionGet());
|
||||
return documents.stream().map(callback::doWith).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doExists(String id, IndexCoordinates index) {
|
||||
GetRequestBuilder getRequestBuilder = requestFactory.getRequestBuilder(client, id, index);
|
||||
getRequestBuilder.setFetchSource(false);
|
||||
return getRequestBuilder.execute().actionGet().isExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,7 +206,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
return doBulkOperation(queries, bulkOptions, index);
|
||||
List<String> ids = doBulkOperation(queries, bulkOptions, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,21 +224,41 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
return client.prepareDelete(index.getIndexName(), IndexCoordinates.TYPE, id).execute().actionGet().getId();
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
DeleteRequestBuilder deleteRequestBuilder = requestFactory.deleteRequestBuilder(client,
|
||||
elasticsearchConverter.convertId(id), index);
|
||||
return deleteRequestBuilder.execute().actionGet().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void delete(DeleteQuery deleteQuery, IndexCoordinates index) {
|
||||
requestFactory.deleteByQueryRequestBuilder(client, deleteQuery, index).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(Object entity, IndexCoordinates index) {
|
||||
return super.delete(entity, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResponse update(UpdateQuery query, IndexCoordinates index) {
|
||||
UpdateRequestBuilder updateRequestBuilder = requestFactory.updateRequestBuilderFor(client, query, index);
|
||||
return updateRequestBuilder.execute().actionGet();
|
||||
org.elasticsearch.action.update.UpdateResponse updateResponse = updateRequestBuilder.execute().actionGet();
|
||||
UpdateResponse.Result result = UpdateResponse.Result.valueOf(updateResponse.getResult().name());
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<String> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
BulkRequestBuilder bulkRequest = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);
|
||||
return checkForBulkOperationFailure(bulkRequest.execute().actionGet());
|
||||
}
|
||||
@@ -205,36 +284,57 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
|
||||
SearchResponse response = getSearchResponse(searchRequestBuilder);
|
||||
return elasticsearchConverter.read(clazz, SearchDocumentResponse.from(response));
|
||||
|
||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScrolledPage<SearchHit<T>> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
public <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
IndexCoordinates index) {
|
||||
Assert.notNull(query.getPageable(), "Query.pageable is required for scan & scroll");
|
||||
|
||||
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
|
||||
searchRequestBuilder.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
||||
SearchResponse response = getSearchResponse(searchRequestBuilder);
|
||||
return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, null);
|
||||
Assert.notNull(query.getPageable(), "pageable of query must not be null.");
|
||||
|
||||
ActionFuture<SearchResponse> action = requestFactory.searchRequestBuilder(client, query, clazz, index) //
|
||||
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)) //
|
||||
.execute();
|
||||
|
||||
SearchResponse response = getSearchResponseWithTimeout(action);
|
||||
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||
index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ScrolledPage<SearchHit<T>> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz) {
|
||||
SearchResponse response = getSearchResponseWithTimeout(
|
||||
client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute());
|
||||
return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, Pageable.unpaged());
|
||||
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
ActionFuture<SearchResponse> action = client //
|
||||
.prepareSearchScroll(scrollId) //
|
||||
.setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)) //
|
||||
.execute();
|
||||
|
||||
SearchResponse response = getSearchResponseWithTimeout(action);
|
||||
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||
index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchScrollClear(String scrollId) {
|
||||
client.prepareClearScroll().addScrollId(scrollId).execute().actionGet();
|
||||
public void searchScrollClear(List<String> scrollIds) {
|
||||
try {
|
||||
client.prepareClearScroll().setScrollIds(scrollIds).execute().actionGet();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not clear scroll: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return client.prepareSearch(index.getIndexNames()).suggest(suggestion).get();
|
||||
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, suggestion, index);
|
||||
return searchRequestBuilder.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,4 +358,41 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return searchTimeout == null ? response.actionGet() : response.actionGet(searchTimeout);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region helper methods
|
||||
@Override
|
||||
protected String getClusterVersion() {
|
||||
|
||||
try {
|
||||
NodesInfoResponse nodesInfoResponse = client.admin().cluster()
|
||||
.nodesInfo(new NodesInfoRequestBuilder(client, NodesInfoAction.INSTANCE).request()).actionGet();
|
||||
if (!nodesInfoResponse.getNodes().isEmpty()) {
|
||||
return nodesInfoResponse.getNodes().get(0).getVersion().toString();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
|
||||
* RuntimeException
|
||||
*
|
||||
* @param exception the Exception to map
|
||||
* @return the potentially translated RuntimeException.
|
||||
* @since 4.0
|
||||
*/
|
||||
private RuntimeException translateException(Exception exception) {
|
||||
|
||||
RuntimeException runtimeException = exception instanceof RuntimeException ? (RuntimeException) exception
|
||||
: new RuntimeException(exception.getMessage(), exception);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator
|
||||
.translateExceptionIfPossible(runtimeException);
|
||||
|
||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,23 +15,19 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
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;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Common operations performed on an entity in the context of it's mapping metadata.
|
||||
@@ -39,19 +35,23 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.2
|
||||
*/
|
||||
class EntityOperations {
|
||||
|
||||
private static final String ID_FIELD = "id";
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
public EntityOperations(
|
||||
@NonNull MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
Assert.notNull(context, "context must not be null");
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private final @NonNull MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Entity} for the given bean.
|
||||
*
|
||||
@@ -99,11 +99,26 @@ class EntityOperations {
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @param type index type override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(Entity, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
|
||||
return determineIndex(entity.getPersistentEntity(), index, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
|
||||
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
|
||||
*
|
||||
* @param entity the entity to determine the index name. Can be {@literal null} if {@code index} and {@literal type}
|
||||
* are provided.
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
*/
|
||||
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index) {
|
||||
return determineIndex(entity.getPersistentEntity(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
|
||||
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
|
||||
@@ -114,20 +129,27 @@ class EntityOperations {
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @param type index type override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(ElasticsearchPersistentEntity, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
|
||||
@Nullable String type) {
|
||||
return persistentEntity.getIndexCoordinates();
|
||||
return determineIndex(persistentEntity, index);
|
||||
}
|
||||
|
||||
private static String indexName(@Nullable ElasticsearchPersistentEntity<?> entity, @Nullable String index) {
|
||||
|
||||
if (StringUtils.isEmpty(index)) {
|
||||
Assert.notNull(entity, "Cannot determine index name");
|
||||
return entity.getIndexCoordinates().getIndexName();
|
||||
}
|
||||
|
||||
return index;
|
||||
/**
|
||||
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
|
||||
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
|
||||
* defined on entity level.
|
||||
*
|
||||
* @param persistentEntity the entity to determine the index name. Can be {@literal null} if {@code index} and
|
||||
* {@literal type} are provided.
|
||||
* @param index index name override can be {@literal null}.
|
||||
* @return the {@link IndexCoordinates} containing index name and index type.
|
||||
* @since 4.1
|
||||
*/
|
||||
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index) {
|
||||
return index != null ? IndexCoordinates.of(index) : persistentEntity.getIndexCoordinates();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,6 +279,22 @@ class EntityOperations {
|
||||
@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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,9 +302,15 @@ 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;
|
||||
|
||||
/*
|
||||
@@ -328,6 +372,16 @@ 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()
|
||||
@@ -406,13 +460,24 @@ 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) {
|
||||
|
||||
@@ -572,6 +637,19 @@ 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()
|
||||
|
||||
@@ -19,13 +19,17 @@ 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
|
||||
@@ -33,204 +37,122 @@ import org.springframework.data.elasticsearch.core.query.AliasQuery;
|
||||
public interface IndexOperations {
|
||||
|
||||
/**
|
||||
* Create an index for given indexName.
|
||||
* Create an index.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index was created
|
||||
*/
|
||||
boolean createIndex(String indexName);
|
||||
boolean create();
|
||||
|
||||
/**
|
||||
* Create an index for given indexName and Settings.
|
||||
* Create an index for given Settings.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @param settings the index settings
|
||||
* @return {@literal true} if the index was created
|
||||
*/
|
||||
boolean createIndex(String indexName, Object settings);
|
||||
boolean create(Document settings);
|
||||
|
||||
/**
|
||||
* Create an index for a class.
|
||||
* Deletes the index this {@link IndexOperations} is bound to
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index was created
|
||||
*/
|
||||
boolean createIndex(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Create an index for given class and Settings.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param settings the index settings
|
||||
* @return {@literal true} if the index was created
|
||||
*/
|
||||
boolean createIndex(Class<?> clazz, Object settings);
|
||||
|
||||
/**
|
||||
* Deletes an index for given entity.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index was deleted
|
||||
*/
|
||||
boolean deleteIndex(Class<?> clazz);
|
||||
boolean delete();
|
||||
|
||||
/**
|
||||
* Deletes an index.
|
||||
* Checks if the index this IndexOperations is bound to exists
|
||||
*
|
||||
* @param indexName the name of the index to delete
|
||||
* @return {@literal true} if the index was deleted
|
||||
*/
|
||||
boolean deleteIndex(String indexName);
|
||||
|
||||
/**
|
||||
* check if index exists.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return {@literal true} if the index exists
|
||||
*/
|
||||
boolean indexExists(String indexName);
|
||||
boolean exists();
|
||||
|
||||
/**
|
||||
* check if index is exists.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return {@literal true} if the index exists
|
||||
* Refresh the index(es) this IndexOperations is bound to
|
||||
*/
|
||||
boolean indexExists(Class<?> clazz);
|
||||
void refresh();
|
||||
|
||||
/**
|
||||
* Create mapping for a class and store it to the index.
|
||||
* Creates the index mapping for the entity this IndexOperations is bound to.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @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 the mapping to the index for the class this IndexOperations is bound to.
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @since 4.1
|
||||
*/
|
||||
default boolean putMapping() {
|
||||
return putMapping(createMapping());}
|
||||
|
||||
/**
|
||||
* writes a mapping to the index
|
||||
*
|
||||
* @param mapping the Document with the mapping definitions
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
boolean putMapping(Class<?> clazz);
|
||||
boolean putMapping(Document mapping);
|
||||
|
||||
/**
|
||||
* Create mapping for the given class and put the mapping to the given index.
|
||||
*
|
||||
* @param index the index to store the mapping to
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* Creates the index mapping for the given class and writes it to the index.
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean putMapping(IndexCoordinates index, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Stores a mapping to an index.
|
||||
*
|
||||
* @param index the index to store the mapping to
|
||||
* @param mappings can be a JSON String or a {@link Map}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
boolean putMapping(IndexCoordinates index, Object mappings);
|
||||
|
||||
/**
|
||||
* Create mapping for a class Stores a mapping to an index.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param mappings can be a JSON String or a {@link Map}
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
<T> boolean putMapping(Class<T> clazz, Object mappings);
|
||||
default boolean putMapping(Class<?> clazz) {
|
||||
return putMapping(createMapping(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping for an index defined by a class.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}.
|
||||
* @return the mapping
|
||||
*/
|
||||
Map<String, Object> getMapping(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Get mapping for a given index.
|
||||
*
|
||||
* @param index the index to read the mapping from
|
||||
* @return the mapping
|
||||
*/
|
||||
Map<String, Object> getMapping(IndexCoordinates index);
|
||||
Map<String, Object> getMapping();
|
||||
|
||||
/**
|
||||
* Add an alias.
|
||||
*
|
||||
* @param query query defining the alias
|
||||
* @param index the index for which to add an alias
|
||||
* @return true if the alias was created
|
||||
*/
|
||||
boolean addAlias(AliasQuery query, IndexCoordinates index);
|
||||
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
|
||||
* @param index the index for which to remove an alias
|
||||
* @return true if the alias was removed
|
||||
*/
|
||||
boolean removeAlias(AliasQuery query, IndexCoordinates index);
|
||||
boolean removeAlias(AliasQuery query);
|
||||
|
||||
/**
|
||||
* Get the alias informations for a specified index.
|
||||
* Get the index settings.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return alias information
|
||||
*/
|
||||
List<AliasMetaData> queryForAlias(String indexName);
|
||||
|
||||
/**
|
||||
* Get settings for a given indexName.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @return the settings
|
||||
*/
|
||||
Map<String, Object> getSettings(String indexName);
|
||||
Map<String, Object> getSettings();
|
||||
|
||||
/**
|
||||
* Get settings for a given indexName.
|
||||
* Get the index settings.
|
||||
*
|
||||
* @param indexName the name of the index
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return the settings
|
||||
*/
|
||||
Map<String, Object> getSettings(String indexName, boolean includeDefaults);
|
||||
|
||||
/**
|
||||
* Get settings for a given class.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @return the settings
|
||||
*/
|
||||
Map<String, Object> getSettings(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Get settings for a given class.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return the settings
|
||||
*/
|
||||
Map<String, Object> getSettings(Class<?> clazz, boolean includeDefaults);
|
||||
|
||||
/**
|
||||
* Refresh the index(es).
|
||||
*
|
||||
* @param index the index to refresh
|
||||
*/
|
||||
void refresh(IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Refresh the index.
|
||||
*
|
||||
* @param clazz The entity class, must be annotated with
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Document}
|
||||
*/
|
||||
void refresh(Class<?> clazz);
|
||||
Map<String, Object> getSettings(boolean includeDefaults);
|
||||
}
|
||||
|
||||
+118
-44
@@ -38,7 +38,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public interface ReactiveDocumentOperations {
|
||||
/**
|
||||
* Index the given entity, once available, extracting index and type from entity metadata.
|
||||
* Index the given entity, once available, extracting index from entity metadata.
|
||||
*
|
||||
* @param entityPublisher must not be {@literal null}.
|
||||
* @param <T>
|
||||
@@ -50,15 +50,6 @@ public interface ReactiveDocumentOperations {
|
||||
return entityPublisher.flatMap(this::save);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given entity extracting index and type from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
<T> Mono<T> save(T entity);
|
||||
|
||||
/**
|
||||
* Index the entity, once available, under the given {@literal type} in the given {@literal index}. If the
|
||||
* {@literal index} is {@literal null} or empty the index name provided via entity metadata is used. Same for the
|
||||
@@ -75,6 +66,15 @@ public interface ReactiveDocumentOperations {
|
||||
return entityPublisher.flatMap(it -> save(it, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return a {@link Mono} emitting the saved entity.
|
||||
*/
|
||||
<T> Mono<T> save(T entity);
|
||||
|
||||
/**
|
||||
* Index the entity under the given {@literal type} in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
|
||||
@@ -87,8 +87,22 @@ public interface ReactiveDocumentOperations {
|
||||
<T> Mono<T> save(T entity, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Index entities under the given {@literal type} in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
|
||||
* Index entities the index extracted from entity metadata.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param clazz the entity class, used to determine the index
|
||||
* @return a {@link Flux} emitting saved entities.
|
||||
* @since 4.1
|
||||
*/
|
||||
default <T> Flux<T> saveAll(Iterable<T> entities, Class<T> clazz) {
|
||||
List<T> entityList = new ArrayList<>();
|
||||
entities.forEach(entityList::add);
|
||||
return saveAll(Mono.just(entityList), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index entities in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -103,8 +117,18 @@ public interface ReactiveDocumentOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Index entities under the given {@literal type} in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used. Same for the {@literal type}.
|
||||
* Index entities in the index extracted from entity metadata.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param clazz the entity class, used to determine the index
|
||||
* @return a {@link Flux} emitting saved entities.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Index entities in the given {@literal index}. If the {@literal index} is
|
||||
* {@literal null} or empty the index name provided via entity metadata is used.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -114,6 +138,16 @@ public interface ReactiveDocumentOperations {
|
||||
*/
|
||||
<T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
* @param query the query defining the ids of the objects to get
|
||||
* @param clazz the type of the object to be returned, used to determine the index
|
||||
* @return flux with list of nullable objects
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Flux<T> multiGet(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute a multiGet against elasticsearch for the given ids.
|
||||
*
|
||||
@@ -151,8 +185,37 @@ public interface ReactiveDocumentOperations {
|
||||
* @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)}
|
||||
*/
|
||||
<T> Mono<T> findById(String id, Class<T> entityType);
|
||||
@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}.
|
||||
@@ -162,7 +225,7 @@ public interface ReactiveDocumentOperations {
|
||||
* @param <T>
|
||||
* @return the {@link Mono} emitting the entity or signalling completion if none found.
|
||||
*/
|
||||
<T> Mono<T> findById(String id, Class<T> entityType, IndexCoordinates index);
|
||||
<T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Check if an entity with given {@literal id} exists.
|
||||
@@ -180,10 +243,21 @@ public interface ReactiveDocumentOperations {
|
||||
* @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.
|
||||
* Delete the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
@@ -191,7 +265,7 @@ public interface ReactiveDocumentOperations {
|
||||
Mono<String> delete(Object entity);
|
||||
|
||||
/**
|
||||
* Delete the given entity extracting index and type from entity metadata.
|
||||
* Delete the given entity extracting index from entity metadata.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
@@ -206,48 +280,48 @@ public interface ReactiveDocumentOperations {
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
*/
|
||||
default Mono<String> deleteById(String id, IndexCoordinates index) {
|
||||
Mono<String> delete(String id, IndexCoordinates index);
|
||||
|
||||
Assert.notNull(index, "Index must not be null!");
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@literal id} of the removed document.
|
||||
* @since 4.0
|
||||
*/
|
||||
Mono<String> delete(String id, Class<?> entityType);
|
||||
|
||||
return deleteById(id, Object.class, index);
|
||||
/**
|
||||
* Delete the entity with given {@literal id} extracting index from entity metadata.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @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 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.
|
||||
*/
|
||||
Mono<String> deleteById(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.
|
||||
*/
|
||||
Mono<String> deleteById(String id, Class<?> entityType, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
Mono<Long> deleteBy(Query query, Class<?> entityType);
|
||||
Mono<Long> delete(Query query, Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Delete the documents matching the given {@link Query} extracting index and type from entity metadata.
|
||||
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @param index the target index, must not be {@literal null}
|
||||
* @return a {@link Mono} emitting the number of the removed documents.
|
||||
*/
|
||||
Mono<Long> deleteBy(Query query, Class<?> entityType, IndexCoordinates index);
|
||||
Mono<Long> delete(Query query, Class<?> entityType, IndexCoordinates index);
|
||||
}
|
||||
|
||||
+38
-1
@@ -40,11 +40,21 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
* Execute within a {@link ClientCallback} managing resources and translating errors.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @param <T> the type the Publisher emits
|
||||
* @return the {@link Publisher} emitting results.
|
||||
*/
|
||||
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
|
||||
|
||||
/**
|
||||
* Execute within a {@link IndicesClientCallback} managing resources and translating errors.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @param <T> the type the Publisher emits
|
||||
* @return the {@link Publisher} emitting results.
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback);
|
||||
|
||||
/**
|
||||
* Get the {@link ElasticsearchConverter} used.
|
||||
*
|
||||
@@ -62,6 +72,22 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
*/
|
||||
IndexCoordinates getIndexCoordinatesFor(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveIndexOperations} that is bound to the given index
|
||||
* @param index IndexCoordinates specifying the index
|
||||
* @return ReactiveIndexOperations implementation
|
||||
* @since 4.1
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveIndexOperations} that is bound to the given class
|
||||
* @param clazz the entity clazz specifiying the index information
|
||||
* @return ReactiveIndexOperations implementation
|
||||
* @since 4.1
|
||||
*/
|
||||
ReactiveIndexOperations indexOps(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient}.
|
||||
@@ -74,4 +100,15 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
|
||||
T doWithClient(ReactiveElasticsearchClient client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface to be used with {@link #executeWithIndicesClient(IndicesClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient.Indices}.
|
||||
*
|
||||
* @param <T> the return type
|
||||
* @since 4.1
|
||||
*/
|
||||
interface IndicesClientCallback<T extends Publisher<?>> {
|
||||
T doWithClient(ReactiveElasticsearchClient.Indices client);
|
||||
}
|
||||
}
|
||||
|
||||
+438
-285
@@ -15,16 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.elasticsearch.index.VersionType.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -41,24 +37,22 @@ import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.client.core.CountRequest;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.WrapperQueryBuilder;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.Entity;
|
||||
@@ -67,6 +61,9 @@ import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchC
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterSaveCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
@@ -74,12 +71,14 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMa
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -91,9 +90,12 @@ import org.springframework.util.Assert;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mathias Teier
|
||||
* @author Aleksei Arsenev
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations {
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
private static final Logger QUERY_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
|
||||
@@ -108,6 +110,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
private @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled();
|
||||
|
||||
private @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||
|
||||
// region Initialization
|
||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) {
|
||||
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
|
||||
@@ -124,6 +128,66 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
this.requestFactory = new RequestFactory(converter);
|
||||
|
||||
logVersions();
|
||||
}
|
||||
|
||||
private void logVersions() {
|
||||
getClusterVersion() //
|
||||
.doOnSuccess(VersionInfo::logVersions) //
|
||||
.doOnError(e -> VersionInfo.logVersions(null)) //
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link RefreshPolicy} to apply when writing to Elasticsearch.
|
||||
*
|
||||
* @param refreshPolicy can be {@literal null}.
|
||||
*/
|
||||
public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current {@link RefreshPolicy}.
|
||||
*/
|
||||
@Nullable
|
||||
public RefreshPolicy getRefreshPolicy() {
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link IndicesOptions} for {@link SearchRequest search requests}.
|
||||
*
|
||||
* @param indicesOptions can be {@literal null}.
|
||||
*/
|
||||
public void setIndicesOptions(@Nullable IndicesOptions indicesOptions) {
|
||||
this.indicesOptions = indicesOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking {@link ReactiveEntityCallbacks callbacks}
|
||||
* like the {@link ReactiveBeforeConvertCallback}.
|
||||
* <p />
|
||||
* Overrides potentially existing {@link ReactiveEntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the given instance is {@literal null}.
|
||||
* @since 4.0
|
||||
*/
|
||||
public void setEntityCallbacks(ReactiveEntityCallbacks entityCallbacks) {
|
||||
|
||||
Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!");
|
||||
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -137,12 +201,15 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
|
||||
AdaptibleEntity<T> adaptableEntity = operations.forEntity(entity, converter.getConversionService());
|
||||
|
||||
return doIndex(entity, adaptableEntity, index) //
|
||||
return maybeCallBeforeConvert(entity, index)
|
||||
.flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) //
|
||||
.map(it -> {
|
||||
return adaptableEntity.populateIdIfNecessary(it.getId());
|
||||
});
|
||||
T savedEntity = it.getT1();
|
||||
IndexResponse indexResponse = it.getT2();
|
||||
AdaptibleEntity<T> adaptableEntity = operations.forEntity(savedEntity, converter.getConversionService());
|
||||
// noinspection ReactiveStreamsNullableInLambdaInTransform
|
||||
return adaptableEntity.populateIdIfNecessary(indexResponse.getId());
|
||||
}).flatMap(saved -> maybeCallAfterSave(saved, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,27 +218,43 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, IndexCoordinates index) {
|
||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz) {
|
||||
return saveAll(entities, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
Assert.notNull(entities, "Entities must not be null!");
|
||||
@Override
|
||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
|
||||
|
||||
return entities.flatMapMany(entityList -> {
|
||||
Assert.notNull(entitiesPublisher, "Entities must not be null!");
|
||||
|
||||
List<AdaptibleEntity<? extends T>> adaptibleEntities = entityList.stream() //
|
||||
.map(e -> operations.forEntity(e, converter.getConversionService())) //
|
||||
.collect(Collectors.toList());
|
||||
Iterator<AdaptibleEntity<? extends T>> iterator = adaptibleEntities.iterator();
|
||||
List<IndexQuery> indexRequests = adaptibleEntities.stream() //
|
||||
.map(e -> getIndexQuery(e.getBean(), e)) //
|
||||
.collect(Collectors.toList());
|
||||
return doBulkOperation(indexRequests, BulkOptions.defaultOptions(), index) //
|
||||
.map(bulkItemResponse -> {
|
||||
return entitiesPublisher //
|
||||
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
|
||||
).collectList() //
|
||||
.map(Entities::new) //
|
||||
.flatMapMany(entities -> {
|
||||
|
||||
AdaptibleEntity<? extends T> mappedEntity = iterator.next();
|
||||
mappedEntity.populateIdIfNecessary(bulkItemResponse.getResponse().getId());
|
||||
return mappedEntity.getBean();
|
||||
});
|
||||
});
|
||||
if (entities.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
||||
.index().flatMap(indexAndResponse -> {
|
||||
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
|
||||
|
||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(savedEntity,
|
||||
converter.getConversionService());
|
||||
adaptibleEntity.populateIdIfNecessary(bulkItemResponse.getResponse().getId());
|
||||
|
||||
return maybeCallAfterSave(savedEntity, index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> multiGet(Query query, Class<T> clazz) {
|
||||
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,16 +265,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notEmpty(query.getIds(), "No Id define for Query");
|
||||
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
|
||||
|
||||
MultiGetRequest request = requestFactory.multiGetRequest(query, index);
|
||||
return Flux.from(execute(client -> client.multiGet(request))) //
|
||||
.handle((result, sink) -> {
|
||||
|
||||
Document document = DocumentAdapters.from(result);
|
||||
T entity = converter.mapDocument(document, clazz);
|
||||
if (entity != null) {
|
||||
sink.next(entity);
|
||||
}
|
||||
});
|
||||
.concatMap(result -> callback.doWith(DocumentAdapters.from(result)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -218,7 +296,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
||||
return client.bulk(bulkRequest) //
|
||||
.onErrorMap(e -> new ElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
|
||||
.onErrorMap(
|
||||
e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
|
||||
.flatMap(this::checkForBulkOperationFailure) //
|
||||
.flatMapMany(response -> Flux.fromArray(response.getItems()));
|
||||
}
|
||||
@@ -233,7 +312,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
failedDocuments.put(item.getId(), item.getFailureMessage());
|
||||
}
|
||||
}
|
||||
ElasticsearchException exception = new ElasticsearchException(
|
||||
BulkFailureException exception = new BulkFailureException(
|
||||
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||
+ failedDocuments + ']',
|
||||
failedDocuments);
|
||||
@@ -243,21 +322,30 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link GetRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
protected Mono<GetResult> doFindById(GetRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.get(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, Class<?> entityType) {
|
||||
return doExists(id, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, Class<?> entityType) {
|
||||
return exists(id, entityType, getIndexCoordinatesFor(entityType));
|
||||
public Mono<Boolean> exists(String id, IndexCoordinates index) {
|
||||
return doExists(id, index);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exists(String, Class, IndexCoordinates)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doExists(id, index);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, index)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,94 +360,79 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.onErrorReturn(NoSuchIndexException.class, false);
|
||||
}
|
||||
|
||||
private Mono<IndexResponse> doIndex(Object value, AdaptibleEntity<?> entity, IndexCoordinates index) {
|
||||
private <T> Mono<Tuple2<T, IndexResponse>> doIndex(T entity, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
IndexRequest request = getIndexRequest(value, entity, index);
|
||||
request = prepareIndexRequest(value, request);
|
||||
return doIndex(request);
|
||||
});
|
||||
IndexRequest request = requestFactory.indexRequest(getIndexQuery(entity), index);
|
||||
request = prepareIndexRequest(entity, request);
|
||||
return Mono.just(entity).zipWith(doIndex(request));
|
||||
}
|
||||
|
||||
private IndexRequest getIndexRequest(Object value, AdaptibleEntity<?> entity, IndexCoordinates index) {
|
||||
Object id = entity.getId();
|
||||
private IndexQuery getIndexQuery(Object value) {
|
||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService());
|
||||
|
||||
IndexRequest request = id != null ? new IndexRequest(index.getIndexName()).id(converter.convertId(id))
|
||||
: new IndexRequest(index.getIndexName());
|
||||
|
||||
request.source(converter.mapObject(value).toJson(), Requests.INDEX_CONTENT_TYPE);
|
||||
|
||||
if (entity.isVersionedEntity()) {
|
||||
|
||||
Number version = entity.getVersion();
|
||||
|
||||
if (version != null) {
|
||||
request.version(version.longValue());
|
||||
request.versionType(EXTERNAL);
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private IndexQuery getIndexQuery(Object value, AdaptibleEntity<?> entity) {
|
||||
Object id = entity.getId();
|
||||
IndexQuery query = new IndexQuery();
|
||||
|
||||
if (id != null) {
|
||||
query.setId(id.toString());
|
||||
}
|
||||
query.setObject(value);
|
||||
|
||||
if (entity.isVersionedEntity()) {
|
||||
boolean usingSeqNo = false;
|
||||
|
||||
if (entity.hasSeqNoPrimaryTerm()) {
|
||||
SeqNoPrimaryTerm seqNoPrimaryTerm = entity.getSeqNoPrimaryTerm();
|
||||
|
||||
if (seqNoPrimaryTerm != null) {
|
||||
query.setSeqNo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
query.setPrimaryTerm(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
usingSeqNo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// seq_no and version are incompatible in the same request
|
||||
if (!usingSeqNo && entity.isVersionedEntity()) {
|
||||
|
||||
Number version = entity.getVersion();
|
||||
|
||||
if (version != null) {
|
||||
query.setVersion(version.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> findById(String id, Class<T> entityType) {
|
||||
return findById(id, entityType, getIndexCoordinatesFor(entityType));
|
||||
public <T> Mono<T> get(String id, Class<T> entityType) {
|
||||
return get(id, entityType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#findById(String, Class, IndexCoordinates)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> findById(String id, Class<T> entityType, IndexCoordinates index) {
|
||||
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doFindById(id, getPersistentEntityFor(entityType), index)
|
||||
.map(it -> converter.mapDocument(DocumentAdapters.from(it), entityType));
|
||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||
|
||||
return doGet(id, getPersistentEntityFor(entityType), index)
|
||||
.flatMap(it -> callback.doWith(DocumentAdapters.from(it)));
|
||||
}
|
||||
|
||||
private Mono<GetResult> doFindById(String id, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
return doFindById(new GetRequest(index.getIndexName(), id));
|
||||
});
|
||||
private Mono<GetResult> doGet(String id, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doGet(requestFactory.getRequest(id, index)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exists(String, Class, IndexCoordinates)
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link GetRequest} ready to be executed.
|
||||
* @return a {@link Mono} emitting the result of the operation.
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, Class<?> entityType, IndexCoordinates index) {
|
||||
protected Mono<GetResult> doGet(GetRequest request) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doExists(id, getPersistentEntityFor(entityType), index);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, ElasticsearchPersistentEntity<?> entity, @Nullable IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> doExists(new GetRequest(index.getIndexName(), id)));
|
||||
return Mono.from(execute(client -> client.get(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -371,21 +444,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
Entity<?> elasticsearchEntity = operations.forEntity(entity);
|
||||
|
||||
return Mono.defer(() -> doDeleteById(entity, converter.convertId(elasticsearchEntity.getId()),
|
||||
elasticsearchEntity.getPersistentEntity(), index));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(String, Class, IndexCoordinates)
|
||||
*/
|
||||
@Override
|
||||
public Mono<String> deleteById(String id, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
return doDeleteById(null, id, getPersistentEntityFor(entityType), index);
|
||||
|
||||
return Mono.defer(() -> doDeleteById(converter.convertId(elasticsearchEntity.getId()), index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -394,44 +453,52 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> deleteById(String id, Class<?> entityType) {
|
||||
return deleteById(id, entityType, getIndexCoordinatesFor(entityType));
|
||||
public Mono<String> delete(String id, Class<?> entityType) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(entityType, "entityType must not be null");
|
||||
|
||||
return delete(id, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
private Mono<String> doDeleteById(@Nullable Object source, String id, ElasticsearchPersistentEntity<?> entity,
|
||||
IndexCoordinates index) {
|
||||
@Override
|
||||
public Mono<String> delete(String id, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return doDeleteById(id, index);
|
||||
}
|
||||
|
||||
private Mono<String> doDeleteById(String id, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
return doDelete(prepareDeleteRequest(source, new DeleteRequest(index.getIndexName(), id)));
|
||||
DeleteRequest request = requestFactory.deleteRequest(id, index);
|
||||
return doDelete(prepareDeleteRequest(request));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#deleteBy(Query, Class, IndexCoordinates)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(Query, Class, IndexCoordinates)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> deleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
public Mono<Long> delete(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return doDeleteBy(query, getPersistentEntityFor(entityType), index).map(BulkByScrollResponse::getDeleted)
|
||||
.publishNext();
|
||||
return doDeleteBy(query, entityType, index).map(BulkByScrollResponse::getDeleted).publishNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> deleteBy(Query query, Class<?> entityType) {
|
||||
return deleteBy(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
public Mono<Long> delete(Query query, Class<?> entityType) {
|
||||
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
private Flux<BulkByScrollResponse> doDeleteBy(Query query, ElasticsearchPersistentEntity<?> entity,
|
||||
IndexCoordinates index) {
|
||||
private Flux<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
return Flux.defer(() -> {
|
||||
DeleteByQueryRequest request = new DeleteByQueryRequest(index.getIndexNames());
|
||||
request.setQuery(mappedQuery(query, entity));
|
||||
|
||||
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
||||
return doDeleteBy(prepareDeleteByRequest(request));
|
||||
});
|
||||
}
|
||||
@@ -473,12 +540,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. Eg. by setting the
|
||||
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
|
||||
*
|
||||
* @param source the source object the {@link DeleteRequest} was derived from. My be {@literal null} if using the
|
||||
* {@literal id} directly.
|
||||
* @param request the generated {@link DeleteRequest}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected DeleteRequest prepareDeleteRequest(@Nullable Object source, DeleteRequest request) {
|
||||
protected DeleteRequest prepareDeleteRequest(DeleteRequest request) {
|
||||
return prepareWriteRequest(request);
|
||||
}
|
||||
|
||||
@@ -491,8 +556,13 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
*/
|
||||
protected DeleteByQueryRequest prepareDeleteByRequest(DeleteByQueryRequest request) {
|
||||
|
||||
if (refreshPolicy != null && !RefreshPolicy.NONE.equals(refreshPolicy)) {
|
||||
request = request.setRefresh(true);
|
||||
if (refreshPolicy != null) {
|
||||
|
||||
if (RefreshPolicy.NONE.equals(refreshPolicy)) {
|
||||
request = request.setRefresh(false);
|
||||
} else {
|
||||
request = request.setRefresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (indicesOptions != null) {
|
||||
@@ -536,8 +606,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
// region SearchOperations
|
||||
@Override
|
||||
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
|
||||
|
||||
return doFind(query, entityType, index).map(searchDocument -> converter.read(resultType, searchDocument));
|
||||
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||
return doFind(query, entityType, index).concatMap(callback::doWith);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -563,6 +633,41 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Aggregation> aggregate(Query query, Class<?> entityType) {
|
||||
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
return doAggregate(query, entityType, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
||||
return doSuggest(suggestion, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return doSuggest(suggestion, index);
|
||||
}
|
||||
|
||||
private Flux<Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(suggestion, index);
|
||||
return Flux.from(execute(client -> client.suggest(request)));
|
||||
});
|
||||
}
|
||||
|
||||
private Flux<Aggregation> doAggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
||||
request = prepareSearchRequest(request);
|
||||
return doAggregate(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count(Query query, Class<?> entityType) {
|
||||
return count(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
@@ -578,49 +683,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
||||
request = prepareSearchRequest(request);
|
||||
request.source().size(0);
|
||||
request.source().trackTotalHits(true);
|
||||
return doCount(request);
|
||||
});
|
||||
}
|
||||
|
||||
private CountRequest buildCountRequest(Query query, ElasticsearchPersistentEntity<?> entity, IndexCoordinates index) {
|
||||
|
||||
CountRequest request = new CountRequest(index.getIndexNames());
|
||||
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
|
||||
searchSourceBuilder.query(mappedQuery(query, entity));
|
||||
searchSourceBuilder.trackScores(query.getTrackScores());
|
||||
|
||||
QueryBuilder postFilterQuery = mappedFilterQuery(query, entity);
|
||||
if (postFilterQuery != null) {
|
||||
searchSourceBuilder.postFilter(postFilterQuery);
|
||||
}
|
||||
|
||||
if (query.getSourceFilter() != null) {
|
||||
searchSourceBuilder.fetchSource(query.getSourceFilter().getIncludes(), query.getSourceFilter().getExcludes());
|
||||
}
|
||||
|
||||
if (query instanceof NativeSearchQuery && ((NativeSearchQuery) query).getCollapseBuilder() != null) {
|
||||
searchSourceBuilder.collapse(((NativeSearchQuery) query).getCollapseBuilder());
|
||||
}
|
||||
|
||||
sort(query, entity).forEach(searchSourceBuilder::sort);
|
||||
|
||||
if (query.getMinScore() > 0) {
|
||||
searchSourceBuilder.minScore(query.getMinScore());
|
||||
}
|
||||
|
||||
if (query.getIndicesOptions() != null) {
|
||||
request.indicesOptions(query.getIndicesOptions());
|
||||
}
|
||||
|
||||
if (query.getPreference() != null) {
|
||||
request.preference(query.getPreference());
|
||||
}
|
||||
request.source(searchSourceBuilder);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
@@ -637,6 +703,22 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
* @param request the already prepared {@link SearchRequest} ready to be executed.
|
||||
* @return a {@link Flux} emitting the result of the operation.
|
||||
*/
|
||||
protected Flux<Aggregation> doAggregate(SearchRequest request) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug("Executing doCount: {}", request);
|
||||
}
|
||||
|
||||
return Flux.from(execute(client -> client.aggregate(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||
*
|
||||
@@ -669,61 +751,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.map(DocumentAdapters::from).onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private QueryBuilder mappedFilterQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
if (query instanceof NativeSearchQuery) {
|
||||
return ((NativeSearchQuery) query).getFilter();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private QueryBuilder mappedQuery(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
QueryBuilder elasticsearchQuery = null;
|
||||
|
||||
if (query instanceof CriteriaQuery) {
|
||||
converter.updateQuery((CriteriaQuery) query, entity.getType());
|
||||
elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(((CriteriaQuery) query).getCriteria());
|
||||
} else if (query instanceof StringQuery) {
|
||||
elasticsearchQuery = new WrapperQueryBuilder(((StringQuery) query).getSource());
|
||||
} else if (query instanceof NativeSearchQuery) {
|
||||
elasticsearchQuery = ((NativeSearchQuery) query).getQuery();
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Unknown query type '%s'.", query.getClass()));
|
||||
}
|
||||
|
||||
return elasticsearchQuery != null ? elasticsearchQuery : QueryBuilders.matchAllQuery();
|
||||
}
|
||||
|
||||
private static List<FieldSortBuilder> sort(Query query, ElasticsearchPersistentEntity<?> entity) {
|
||||
|
||||
if (query.getSort() == null || query.getSort().isUnsorted()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<FieldSortBuilder> mappedSort = new ArrayList<>();
|
||||
for (Sort.Order order : query.getSort()) {
|
||||
|
||||
ElasticsearchPersistentProperty property = entity.getPersistentProperty(order.getProperty());
|
||||
String fieldName = property != null ? property.getFieldName() : order.getProperty();
|
||||
|
||||
FieldSortBuilder sort = SortBuilders.fieldSort(fieldName)
|
||||
.order(order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC);
|
||||
|
||||
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
|
||||
sort.missing("_first");
|
||||
} else if (order.getNullHandling() == Sort.NullHandling.NULLS_LAST) {
|
||||
sort.missing("_last");
|
||||
}
|
||||
|
||||
mappedSort.add(sort);
|
||||
}
|
||||
|
||||
return mappedSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
|
||||
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
|
||||
@@ -743,44 +770,40 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
// Property Setters / Getters
|
||||
|
||||
/**
|
||||
* Set the default {@link RefreshPolicy} to apply when writing to Elasticsearch.
|
||||
*
|
||||
* @param refreshPolicy can be {@literal null}.
|
||||
*/
|
||||
public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) {
|
||||
this.refreshPolicy = refreshPolicy;
|
||||
protected Mono<String> getClusterVersion() {
|
||||
try {
|
||||
return Mono.from(execute(client -> client.info())).map(mainResponse -> mainResponse.getVersion().toString());
|
||||
} catch (Exception ignored) {}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link IndicesOptions} for {@link SearchRequest search requests}.
|
||||
*
|
||||
* @param indicesOptions can be {@literal null}.
|
||||
*/
|
||||
public void setIndicesOptions(@Nullable IndicesOptions indicesOptions) {
|
||||
this.indicesOptions = indicesOptions;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#exctute(ClientCallback)
|
||||
*/
|
||||
@Override
|
||||
public <T> Publisher<T> execute(ClientCallback<Publisher<T>> callback) {
|
||||
return Flux.defer(() -> callback.doWithClient(getClient())).onErrorMap(this::translateException);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#getElasticsearchConverter()
|
||||
*/
|
||||
@Override
|
||||
public <T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback) {
|
||||
return Flux.defer(() -> callback.doWithClient(getIndicesClient())).onErrorMap(this::translateException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchConverter getElasticsearchConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
||||
return new DefaultReactiveIndexOperations(this, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveIndexOperations indexOps(Class<?> clazz) {
|
||||
return new DefaultReactiveIndexOperations(this, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||
return getPersistentEntityFor(clazz).getIndexCoordinates();
|
||||
@@ -792,15 +815,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return type != null ? mappingContext.getPersistentEntity(type) : null;
|
||||
}
|
||||
|
||||
private Throwable translateException(Throwable throwable) {
|
||||
|
||||
RuntimeException exception = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||
: new RuntimeException(throwable.getMessage(), throwable);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator.translateExceptionIfPossible(exception);
|
||||
|
||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link ReactiveElasticsearchClient} to operate upon.
|
||||
*
|
||||
@@ -810,6 +824,145 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return this.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link ReactiveElasticsearchClient.Indices} to operate upon.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
protected ReactiveElasticsearchClient.Indices getIndicesClient() {
|
||||
|
||||
if (client instanceof ReactiveElasticsearchClient.Indices) {
|
||||
return (ReactiveElasticsearchClient.Indices) client;
|
||||
}
|
||||
|
||||
throw new UncategorizedElasticsearchException("No ReactiveElasticsearchClient.Indices implementation available");
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
|
||||
* RuntimeException
|
||||
*
|
||||
* @param throwable the Throwable to map
|
||||
* @return the potentially translated RuntimeException.
|
||||
* @since 4.0
|
||||
*/
|
||||
private RuntimeException translateException(Throwable throwable) {
|
||||
|
||||
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||
: new RuntimeException(throwable.getMessage(), throwable);
|
||||
RuntimeException potentiallyTranslatedException = exceptionTranslator
|
||||
.translateExceptionIfPossible(runtimeException);
|
||||
|
||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException;
|
||||
}
|
||||
|
||||
// region callbacks
|
||||
protected <T> Mono<T> maybeCallBeforeConvert(T entity, IndexCoordinates index) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(ReactiveBeforeConvertCallback.class, entity, index);
|
||||
}
|
||||
|
||||
return Mono.just(entity);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterSave(T entity, IndexCoordinates index) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(ReactiveAfterSaveCallback.class, entity, index);
|
||||
}
|
||||
|
||||
return Mono.just(entity);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterConvert(T entity, Document document, IndexCoordinates index) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(ReactiveAfterConvertCallback.class, entity, document, index);
|
||||
}
|
||||
|
||||
return Mono.just(entity);
|
||||
}
|
||||
// endregion
|
||||
|
||||
protected interface DocumentCallback<T> {
|
||||
|
||||
@NonNull
|
||||
Mono<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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Mono<T> doWith(@Nullable Document document) {
|
||||
if (document == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
T entity = reader.read(type, document);
|
||||
return maybeCallAfterConvert(entity, document, index);
|
||||
}
|
||||
}
|
||||
|
||||
protected interface SearchDocumentCallback<T> {
|
||||
|
||||
@NonNull
|
||||
Mono<SearchHit<T>> doWith(@NonNull SearchDocument response);
|
||||
}
|
||||
|
||||
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
|
||||
private final DocumentCallback<T> delegate;
|
||||
private final Class<T> type;
|
||||
|
||||
public ReadSearchDocumentCallback(Class<T> type, IndexCoordinates index) {
|
||||
Assert.notNull(type, "type is null");
|
||||
|
||||
this.delegate = new ReadDocumentCallback<>(converter, type, index);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SearchHit<T>> doWith(SearchDocument response) {
|
||||
return delegate.doWith(response)
|
||||
.map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity));
|
||||
}
|
||||
}
|
||||
|
||||
private class Entities<T> {
|
||||
private final List<T> entities;
|
||||
|
||||
private Entities(List<T> entities) {
|
||||
Assert.notNull(entities, "entities cannot be null");
|
||||
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return entities.isEmpty();
|
||||
}
|
||||
|
||||
private List<IndexQuery> indexQueries() {
|
||||
return entities.stream().map(ReactiveElasticsearchTemplate.this::getIndexQuery).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private T entityAt(long index) {
|
||||
// it's safe to cast to int because the original indexed colleciton was fitting in memory
|
||||
int intIndex = (int) index;
|
||||
return entities.get(intIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
|
||||
/**
|
||||
* Interface defining operations on indexes for the reactive stack.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface ReactiveIndexOperations {
|
||||
|
||||
/**
|
||||
* Create an index.
|
||||
*
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg.
|
||||
* the index already exist.
|
||||
*/
|
||||
Mono<Boolean> create();
|
||||
|
||||
/**
|
||||
* Create an index with the specified settings.
|
||||
*
|
||||
* @param settings index settings
|
||||
* @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg.
|
||||
* the index already exist.
|
||||
*/
|
||||
Mono<Boolean> create(Document settings);
|
||||
|
||||
/**
|
||||
* Delete an index.
|
||||
*
|
||||
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error}. If the index does
|
||||
* not exist, a value of {@literal false is emitted}.
|
||||
*/
|
||||
Mono<Boolean> delete();
|
||||
|
||||
/**
|
||||
* checks if an index exists
|
||||
*
|
||||
* @return a {@link Mono} with the result of exist check
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
/**
|
||||
* Refresh the index(es) this IndexOperations is bound to
|
||||
*
|
||||
* @return a {@link Mono} signalling operation completion.
|
||||
*/
|
||||
Mono<Void> refresh();
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the entity this IndexOperations is bound to.
|
||||
*
|
||||
* @return mapping object
|
||||
*/
|
||||
Mono<Document> createMapping();
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the given class
|
||||
*
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return a {@link Mono} with the mapping document
|
||||
*/
|
||||
Mono<Document> createMapping(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Writes the mapping to the index for the class this IndexOperations is bound to.
|
||||
*
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
default Mono<Boolean> putMapping() {
|
||||
return putMapping(createMapping());
|
||||
}
|
||||
|
||||
/**
|
||||
* writes a mapping to the index
|
||||
*
|
||||
* @param mapping the Document with the mapping definitions
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
Mono<Boolean> putMapping(Mono<Document> mapping);
|
||||
|
||||
/**
|
||||
* Creates the index mapping for the given class and writes it to the index.
|
||||
*
|
||||
* @param clazz the clazz to create a mapping for
|
||||
* @return {@literal true} if the mapping could be stored
|
||||
*/
|
||||
default Mono<Boolean> putMapping(Class<?> clazz) {
|
||||
return putMapping(createMapping(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mapping for the index targeted defined by this {@link ReactiveIndexOperations}
|
||||
*
|
||||
* @return the mapping
|
||||
*/
|
||||
Mono<Document> getMapping();
|
||||
|
||||
/**
|
||||
* get the settings for the index
|
||||
*
|
||||
* @return a {@link Mono} with a {@link Document} containing the index settings
|
||||
*/
|
||||
default Mono<Document> getSettings() {
|
||||
return getSettings(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the settings for the index
|
||||
*
|
||||
* @param includeDefaults whether or not to include all the default settings
|
||||
* @return a {@link Mono} with a {@link Document} containing the index settings
|
||||
*/
|
||||
Mono<Document> getSettings(boolean includeDefaults);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
|
||||
/**
|
||||
* Utility to reactively read {@link org.springframework.core.io.Resource}s.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class ReactiveResourceUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveResourceUtil.class);
|
||||
private static final int BUFFER_SIZE = 8_192;
|
||||
|
||||
/**
|
||||
* Read a {@link ClassPathResource} into a {@link reactor.core.publisher.Mono<String>}.
|
||||
*
|
||||
* @param url the resource to read
|
||||
* @return a {@link reactor.core.publisher.Mono} emitting the resources content or an empty Mono on error
|
||||
*/
|
||||
public static Mono<String> readFileFromClasspath(String url) {
|
||||
|
||||
return DataBufferUtils
|
||||
.join(DataBufferUtils.read(new ClassPathResource(url), new DefaultDataBufferFactory(), BUFFER_SIZE))
|
||||
.<String> handle((it, sink) -> {
|
||||
|
||||
try (InputStream is = it.asInputStream();
|
||||
InputStreamReader in = new InputStreamReader(is, Charset.defaultCharset());
|
||||
BufferedReader br = new BufferedReader(in)) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
|
||||
sink.next(sb.toString());
|
||||
sink.complete();
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage()));
|
||||
sink.complete();
|
||||
} finally {
|
||||
DataBufferUtils.release(it);
|
||||
}
|
||||
}).onErrorResume(throwable -> {
|
||||
LOGGER.debug(String.format("Failed to load file from url: %s: %s", url, throwable.getMessage()));
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
// Utility constructor
|
||||
private ReactiveResourceUtil() {}
|
||||
}
|
||||
+44
@@ -19,6 +19,9 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.elasticsearch.search.suggest.Suggest;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
@@ -30,6 +33,8 @@ import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
* APIs</a>.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface ReactiveSearchOperations {
|
||||
@@ -183,4 +188,43 @@ public interface ReactiveSearchOperations {
|
||||
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);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param entityType must not be {@literal null}.
|
||||
* @return the suggest response
|
||||
*/
|
||||
Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param index the index to run the query against
|
||||
* @return the suggest response
|
||||
*/
|
||||
Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,14 +2,15 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @deprecated since 4.0, will be removed in a future version.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ScrolledPage<T> extends Page<T> {
|
||||
|
||||
@Nullable
|
||||
String getScrollId();
|
||||
String getScrollId();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -35,24 +36,50 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SearchHit<T> {
|
||||
|
||||
private final String id;
|
||||
@Nullable private final String index;
|
||||
@Nullable private final String id;
|
||||
private final float score;
|
||||
private final List<Object> sortValues;
|
||||
private final T content;
|
||||
private final Map<String, List<String>> highlightFields = new LinkedHashMap<>();
|
||||
private final Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
|
||||
public SearchHit(@Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, T content) {
|
||||
this(index, id, score, sortValues, highlightFields, null, null, content);
|
||||
}
|
||||
|
||||
public SearchHit(@Nullable String index, @Nullable String id, float score, @Nullable Object[] sortValues,
|
||||
@Nullable Map<String, List<String>> highlightFields, @Nullable Map<String, SearchHits<?>> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData, T content) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.score = score;
|
||||
this.sortValues = (sortValues != null) ? Arrays.asList(sortValues) : new ArrayList<>();
|
||||
|
||||
if (highlightFields != null) {
|
||||
this.highlightFields.putAll(highlightFields);
|
||||
}
|
||||
|
||||
if (innerHits != null) {
|
||||
this.innerHits.putAll(innerHits);
|
||||
}
|
||||
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index name where the hit's document was found
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
@@ -79,6 +106,9 @@ public class SearchHit<T> {
|
||||
return Collections.unmodifiableList(sortValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map from field names to highlight values, never {@literal null}
|
||||
*/
|
||||
public Map<String, List<String>> getHighlightFields() {
|
||||
return Collections.unmodifiableMap(highlightFields.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> Collections.unmodifiableList(entry.getValue()))));
|
||||
@@ -97,6 +127,39 @@ public class SearchHit<T> {
|
||||
return Collections.unmodifiableList(highlightFields.getOrDefault(field, Collections.emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the {@link SearchHits} for the inner hits with the given name. If the inner hits could be mapped to a
|
||||
* nested entity class, the returned data will be of this type, otherwise
|
||||
* {{@link org.springframework.data.elasticsearch.core.document.SearchDocument}} instances are returned in this
|
||||
* {@link SearchHits} object.
|
||||
*
|
||||
* @param name the inner hits name
|
||||
* @return {@link SearchHits} if available, otherwise {@literal null}
|
||||
*/
|
||||
@Nullable
|
||||
public SearchHits<?> getInnerHits(String name) {
|
||||
return innerHits.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map from inner_hits names to inner hits, in a {@link SearchHits} object, never {@literal null}
|
||||
* @since 4.1
|
||||
*/
|
||||
public Map<String, SearchHits<?>> getInnerHits() {
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a nested inner hit, return the nested metadata information
|
||||
*
|
||||
* @return {{@link NestedMetaData}
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
public NestedMetaData getNestedMetaData() {
|
||||
return nestedMetaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHit{" + "id='" + id + '\'' + ", score=" + score + ", sortValues=" + sortValues + ", content="
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
class SearchHitMapping<T> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SearchHitMapping.class);
|
||||
|
||||
private final Class<T> type;
|
||||
private final ElasticsearchConverter converter;
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
private SearchHitMapping(Class<T> type, ElasticsearchConverter converter) {
|
||||
Assert.notNull(type, "type is null");
|
||||
Assert.notNull(converter, "converter is null");
|
||||
|
||||
this.type = type;
|
||||
this.converter = converter;
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
}
|
||||
|
||||
static <T> SearchHitMapping<T> mappingFor(Class<T> entityClass, ElasticsearchConverter converter) {
|
||||
return new SearchHitMapping<>(entityClass, converter);
|
||||
}
|
||||
|
||||
SearchHits<T> mapHits(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
|
||||
return mapHitsFromResponse(searchDocumentResponse, contents);
|
||||
}
|
||||
|
||||
SearchScrollHits<T> mapScrollHits(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
|
||||
return mapHitsFromResponse(searchDocumentResponse, contents);
|
||||
}
|
||||
|
||||
private SearchHitsImpl<T> mapHitsFromResponse(SearchDocumentResponse searchDocumentResponse, List<T> contents) {
|
||||
|
||||
Assert.notNull(searchDocumentResponse, "searchDocumentResponse is null");
|
||||
Assert.notNull(contents, "contents is null");
|
||||
|
||||
Assert.isTrue(searchDocumentResponse.getSearchDocuments().size() == contents.size(),
|
||||
"Count of documents must match the count of entities");
|
||||
|
||||
long totalHits = searchDocumentResponse.getTotalHits();
|
||||
float maxScore = searchDocumentResponse.getMaxScore();
|
||||
String scrollId = searchDocumentResponse.getScrollId();
|
||||
|
||||
List<SearchHit<T>> searchHits = new ArrayList<>();
|
||||
List<SearchDocument> searchDocuments = searchDocumentResponse.getSearchDocuments();
|
||||
for (int i = 0; i < searchDocuments.size(); i++) {
|
||||
SearchDocument document = searchDocuments.get(i);
|
||||
T content = contents.get(i);
|
||||
SearchHit<T> hit = mapHit(document, content);
|
||||
searchHits.add(hit);
|
||||
}
|
||||
Aggregations aggregations = searchDocumentResponse.getAggregations();
|
||||
TotalHitsRelation totalHitsRelation = TotalHitsRelation.valueOf(searchDocumentResponse.getTotalHitsRelation());
|
||||
|
||||
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations);
|
||||
}
|
||||
|
||||
SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
|
||||
|
||||
Assert.notNull(searchDocument, "searchDocument is null");
|
||||
Assert.notNull(content, "content is null");
|
||||
|
||||
return new SearchHit<T>(searchDocument.getIndex(), //
|
||||
searchDocument.hasId() ? searchDocument.getId() : null, //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
getHighlightsAndRemapFieldNames(searchDocument), //
|
||||
mapInnerHits(searchDocument), //
|
||||
searchDocument.getNestedMetaData(), //
|
||||
content); //
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, List<String>> getHighlightsAndRemapFieldNames(SearchDocument searchDocument) {
|
||||
Map<String, List<String>> highlightFields = searchDocument.getHighlightFields();
|
||||
|
||||
if (highlightFields == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
|
||||
if (persistentEntity == null) {
|
||||
return highlightFields;
|
||||
}
|
||||
|
||||
return highlightFields.entrySet().stream().collect(Collectors.toMap(entry -> {
|
||||
ElasticsearchPersistentProperty property = persistentEntity.getPersistentPropertyWithFieldName(entry.getKey());
|
||||
return property != null ? property.getName() : entry.getKey();
|
||||
}, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map<String, SearchHits<?>> mapInnerHits(SearchDocument searchDocument) {
|
||||
|
||||
Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
|
||||
Map<String, SearchDocumentResponse> documentInnerHits = searchDocument.getInnerHits();
|
||||
|
||||
if (documentInnerHits != null && documentInnerHits.size() > 0) {
|
||||
|
||||
SearchHitMapping<SearchDocument> searchDocumentSearchHitMapping = SearchHitMapping
|
||||
.mappingFor(SearchDocument.class, converter);
|
||||
|
||||
for (Map.Entry<String, SearchDocumentResponse> entry : documentInnerHits.entrySet()) {
|
||||
SearchDocumentResponse searchDocumentResponse = entry.getValue();
|
||||
|
||||
SearchHits<SearchDocument> searchHits = searchDocumentSearchHitMapping
|
||||
.mapHitsFromResponse(searchDocumentResponse, searchDocumentResponse.getSearchDocuments());
|
||||
|
||||
// map Documents to real objects
|
||||
SearchHits<?> mappedSearchHits = mapInnerDocuments(searchHits, type);
|
||||
|
||||
innerHits.put(entry.getKey(), mappedSearchHits);
|
||||
}
|
||||
|
||||
}
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to convert the SearchDocument instances to instances of the inner property class.
|
||||
*
|
||||
* @param searchHits {@link SearchHits} containing {@link Document} instances
|
||||
* @param type the class of the containing class
|
||||
* @return a new {@link SearchHits} instance containing the mapped objects or the original inout if any error occurs
|
||||
*/
|
||||
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
|
||||
|
||||
if (searchHits.getTotalHits() == 0) {
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
try {
|
||||
NestedMetaData nestedMetaData = searchHits.getSearchHit(0).getContent().getNestedMetaData();
|
||||
ElasticsearchPersistentEntityWithNestedMetaData persistentEntityWithNestedMetaData = getPersistentEntity(
|
||||
mappingContext.getPersistentEntity(type), nestedMetaData);
|
||||
|
||||
if (persistentEntityWithNestedMetaData.entity != null) {
|
||||
List<SearchHit<Object>> convertedSearchHits = new ArrayList<>();
|
||||
Class<?> targetType = persistentEntityWithNestedMetaData.entity.getType();
|
||||
|
||||
// convert the list of SearchHit<SearchDocument> to list of SearchHit<Object>
|
||||
searchHits.getSearchHits().forEach(searchHit -> {
|
||||
SearchDocument searchDocument = searchHit.getContent();
|
||||
|
||||
Object targetObject = converter.read(targetType, searchDocument);
|
||||
convertedSearchHits.add(new SearchHit<Object>(searchDocument.getIndex(), //
|
||||
searchDocument.getId(), //
|
||||
searchDocument.getScore(), //
|
||||
searchDocument.getSortValues(), //
|
||||
searchDocument.getHighlightFields(), //
|
||||
searchHit.getInnerHits(), //
|
||||
persistentEntityWithNestedMetaData.nestedMetaData, //
|
||||
targetObject));
|
||||
});
|
||||
|
||||
String scrollId = null;
|
||||
if (searchHits instanceof SearchHitsImpl) {
|
||||
scrollId = ((SearchHitsImpl<?>) searchHits).getScrollId();
|
||||
}
|
||||
|
||||
return new SearchHitsImpl<>(searchHits.getTotalHits(), //
|
||||
searchHits.getTotalHitsRelation(), //
|
||||
searchHits.getMaxScore(), //
|
||||
scrollId, //
|
||||
convertedSearchHits, //
|
||||
searchHits.getAggregations());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Could not map inner_hits", e);
|
||||
}
|
||||
|
||||
return searchHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* find a {@link ElasticsearchPersistentEntity} following the property chain defined by the nested metadata
|
||||
*
|
||||
* @param persistentEntity base entity
|
||||
* @param nestedMetaData nested metadata
|
||||
* @return A {@link ElasticsearchPersistentEntityWithNestedMetaData} containing the found entity or null together with
|
||||
* the {@link NestedMetaData} that has mapped field names.
|
||||
*/
|
||||
private ElasticsearchPersistentEntityWithNestedMetaData getPersistentEntity(
|
||||
@Nullable ElasticsearchPersistentEntity<?> persistentEntity, @Nullable NestedMetaData nestedMetaData) {
|
||||
|
||||
NestedMetaData currentMetaData = nestedMetaData;
|
||||
List<NestedMetaData> mappedNestedMetaDatas = new LinkedList<>();
|
||||
|
||||
while (persistentEntity != null && currentMetaData != null) {
|
||||
ElasticsearchPersistentProperty persistentProperty = persistentEntity
|
||||
.getPersistentPropertyWithFieldName(currentMetaData.getField());
|
||||
|
||||
if (persistentProperty == null) {
|
||||
persistentEntity = null;
|
||||
} else {
|
||||
persistentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType());
|
||||
mappedNestedMetaDatas.add(0,
|
||||
NestedMetaData.of(persistentProperty.getName(), currentMetaData.getOffset(), null));
|
||||
currentMetaData = currentMetaData.getChild();
|
||||
}
|
||||
}
|
||||
|
||||
NestedMetaData mappedNestedMetaData = mappedNestedMetaDatas.stream().reduce(null,
|
||||
(result, nmd) -> NestedMetaData.of(nmd.getField(), nmd.getOffset(), result));
|
||||
|
||||
return new ElasticsearchPersistentEntityWithNestedMetaData(persistentEntity, mappedNestedMetaData);
|
||||
}
|
||||
|
||||
private static class ElasticsearchPersistentEntityWithNestedMetaData {
|
||||
@Nullable private ElasticsearchPersistentEntity<?> entity;
|
||||
private NestedMetaData nestedMetaData;
|
||||
|
||||
public ElasticsearchPersistentEntityWithNestedMetaData(@Nullable ElasticsearchPersistentEntity<?> entity,
|
||||
NestedMetaData nestedMetaData) {
|
||||
this.entity = entity;
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,20 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.aggregation.impl.AggregatedPageImpl;
|
||||
import org.springframework.data.repository.util.ReactiveWrappers;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class with helper methods for working with {@link SearchHit}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
public final class SearchHitSupport {
|
||||
@@ -61,8 +67,8 @@ public final class SearchHitSupport {
|
||||
if (result instanceof AggregatedPage<?>) {
|
||||
AggregatedPage<?> page = (AggregatedPage<?>) result;
|
||||
List<?> list = page.getContent().stream().map(SearchHitSupport::unwrapSearchHits).collect(Collectors.toList());
|
||||
return new AggregatedPageImpl<>(list, null, page.getTotalElements(), page.getAggregations(), page.getScrollId(),
|
||||
page.getMaxScore());
|
||||
return new AggregatedPageImpl<>(list, page.getPageable(), page.getTotalElements(), page.getAggregations(),
|
||||
page.getScrollId(), page.getMaxScore());
|
||||
|
||||
}
|
||||
|
||||
@@ -75,23 +81,81 @@ public final class SearchHitSupport {
|
||||
return unwrapSearchHits(searchHits.getSearchHits());
|
||||
}
|
||||
|
||||
if (result instanceof Flux) {
|
||||
Flux<?> flux = (Flux<?>) result;
|
||||
return flux.map(SearchHitSupport::unwrapSearchHits);
|
||||
if (result instanceof SearchHitsIterator<?>) {
|
||||
return unwrapSearchHitsIterator((SearchHitsIterator<?>) result);
|
||||
}
|
||||
|
||||
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
|
||||
|
||||
if (result instanceof Flux) {
|
||||
Flux<?> flux = (Flux<?>) result;
|
||||
return flux.map(SearchHitSupport::unwrapSearchHits);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CloseableIterator<?> unwrapSearchHitsIterator(SearchHitsIterator<?> iterator) {
|
||||
|
||||
return new CloseableIterator<Object>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
return unwrapSearchHits(iterator.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
iterator.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link AggregatedPage} with the {@link SearchHit} objects from a {@link SearchHits} object.
|
||||
*
|
||||
* @param searchHits, must not be {@literal null}.
|
||||
* @param pageable, must not be {@literal null}.
|
||||
* @return the created Page
|
||||
* @deprecated since 4.0, will be removed in a future version.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> AggregatedPage<SearchHit<T>> page(SearchHits<T> searchHits, Pageable pageable) {
|
||||
return new AggregatedPageImpl<>(searchHits.getSearchHits(), pageable, searchHits.getTotalHits(),
|
||||
searchHits.getAggregations(), searchHits.getScrollId(), searchHits.getMaxScore());
|
||||
return new AggregatedPageImpl<>( //
|
||||
searchHits.getSearchHits(), //
|
||||
pageable, //
|
||||
searchHits.getTotalHits(), //
|
||||
searchHits.getAggregations(), //
|
||||
null, //
|
||||
searchHits.getMaxScore());
|
||||
}
|
||||
|
||||
public static <T> SearchPage<T> searchPageFor(SearchHits<T> searchHits, @Nullable Pageable pageable) {
|
||||
return new SearchPageImpl<>(searchHits, (pageable != null) ? pageable : Pageable.unpaged());
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchPage implementation.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
static class SearchPageImpl<T> extends PageImpl<SearchHit<T>> implements SearchPage<T> {
|
||||
|
||||
private final SearchHits<T> searchHits;
|
||||
|
||||
public SearchPageImpl(SearchHits<T> searchHits, Pageable pageable) {
|
||||
super(searchHits.getSearchHits(), pageable, searchHits.getTotalHits());
|
||||
this.searchHits = searchHits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchHits<T> getSearchHits() {
|
||||
return searchHits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,143 +15,74 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates a list of {@link SearchHit}s with additional information from the search.
|
||||
*
|
||||
*
|
||||
* @param <T> the result data class.
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchHits<T> implements Streamable<SearchHit<T>> {
|
||||
|
||||
private final long totalHits;
|
||||
private final TotalHitsRelation totalHitsRelation;
|
||||
private final float maxScore;
|
||||
private final String scrollId;
|
||||
private final List<? extends SearchHit<T>> searchHits;
|
||||
private final Aggregations aggregations;
|
||||
|
||||
/**
|
||||
* @param totalHits the number of total hits for the search
|
||||
* @param totalHitsRelation the relation {@see TotalHitsRelation}, must not be {@literal null}
|
||||
* @param maxScore the maximum score
|
||||
* @param scrollId the scroll id if available
|
||||
* @param searchHits must not be {@literal null}
|
||||
* @param aggregations the aggregations if available
|
||||
*/
|
||||
public SearchHits(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||
List<? extends SearchHit<T>> searchHits, @Nullable Aggregations aggregations) {
|
||||
|
||||
Assert.notNull(searchHits, "searchHits must not be null");
|
||||
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
this.scrollId = scrollId;
|
||||
this.searchHits = searchHits;
|
||||
this.aggregations = aggregations;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterator<SearchHit<T>> iterator() {
|
||||
return (Iterator<SearchHit<T>>) searchHits.iterator();
|
||||
}
|
||||
|
||||
// region getter
|
||||
/**
|
||||
* @return the number of total hits.
|
||||
*/
|
||||
public long getTotalHits() {
|
||||
return totalHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the relation for the total hits
|
||||
*/
|
||||
public TotalHitsRelation getTotalHitsRelation() {
|
||||
return totalHitsRelation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum score
|
||||
*/
|
||||
public float getMaxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scroll id
|
||||
*/
|
||||
@Nullable
|
||||
public String getScrollId() {
|
||||
return scrollId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the contained {@link SearchHit}s.
|
||||
*/
|
||||
public List<SearchHit<T>> getSearchHits() {
|
||||
return Collections.unmodifiableList(searchHits);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region SearchHit access
|
||||
/**
|
||||
* @param index position in List.
|
||||
* @return the {@link SearchHit} at position {index}
|
||||
* @throws IndexOutOfBoundsException on invalid index
|
||||
*/
|
||||
public SearchHit<T> getSearchHit(int index) {
|
||||
return searchHits.get(index);
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHits{" + //
|
||||
"totalHits=" + totalHits + //
|
||||
", totalHitsRelation=" + totalHitsRelation + //
|
||||
", maxScore=" + maxScore + //
|
||||
", scrollId='" + scrollId + '\'' + //
|
||||
", searchHits={" + searchHits.size() + " elements}" + //
|
||||
", aggregations=" + aggregations + //
|
||||
'}';
|
||||
}
|
||||
|
||||
// region aggregations
|
||||
/**
|
||||
* @return true if aggregations are available
|
||||
*/
|
||||
public boolean hasAggregations() {
|
||||
return aggregations != null;
|
||||
}
|
||||
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
|
||||
|
||||
/**
|
||||
* @return the aggregations.
|
||||
*/
|
||||
@Nullable
|
||||
public Aggregations getAggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
// endregion
|
||||
Aggregations getAggregations();
|
||||
|
||||
/**
|
||||
* Enum to represent the relation that Elasticsearch returns for the totalHits value {@see <a href=
|
||||
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-track-total-hits">Ekasticsearch
|
||||
* docs</a>}
|
||||
* @return the maximum score
|
||||
*/
|
||||
public enum TotalHitsRelation {
|
||||
EQUAL_TO, GREATER_THAN_OR_EQUAL_TO
|
||||
float getMaxScore();
|
||||
|
||||
/**
|
||||
* @param index position in List.
|
||||
* @return the {@link SearchHit} at position {index}
|
||||
* @throws IndexOutOfBoundsException on invalid index
|
||||
*/
|
||||
SearchHit<T> getSearchHit(int index);
|
||||
|
||||
/**
|
||||
* @return the contained {@link SearchHit}s.
|
||||
*/
|
||||
List<SearchHit<T>> getSearchHits();
|
||||
|
||||
/**
|
||||
* @return the number of total hits.
|
||||
*/
|
||||
long getTotalHits();
|
||||
|
||||
/**
|
||||
* @return the relation for the total hits
|
||||
*/
|
||||
TotalHitsRelation getTotalHitsRelation();
|
||||
|
||||
/**
|
||||
* @return true if aggregations are available
|
||||
*/
|
||||
default boolean hasAggregations() {
|
||||
return getAggregations() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the {@link SearchHits} has search hits.
|
||||
*/
|
||||
default boolean hasSearchHits() {
|
||||
return !getSearchHits().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an iterator for {@link SearchHit}
|
||||
*/
|
||||
default Iterator<SearchHit<T>> iterator() {
|
||||
return getSearchHits().iterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link SearchScrollHits}
|
||||
*
|
||||
* @param <T> the result data class.
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
|
||||
|
||||
private final long totalHits;
|
||||
private final TotalHitsRelation totalHitsRelation;
|
||||
private final float maxScore;
|
||||
private final String scrollId;
|
||||
private final List<? extends SearchHit<T>> searchHits;
|
||||
private final Aggregations aggregations;
|
||||
|
||||
/**
|
||||
* @param totalHits the number of total hits for the search
|
||||
* @param totalHitsRelation the relation {@see TotalHitsRelation}, must not be {@literal null}
|
||||
* @param maxScore the maximum score
|
||||
* @param scrollId the scroll id if available
|
||||
* @param searchHits must not be {@literal null}
|
||||
* @param aggregations the aggregations if available
|
||||
*/
|
||||
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||
List<? extends SearchHit<T>> searchHits, @Nullable Aggregations aggregations) {
|
||||
|
||||
Assert.notNull(searchHits, "searchHits must not be null");
|
||||
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
this.scrollId = scrollId;
|
||||
this.searchHits = searchHits;
|
||||
this.aggregations = aggregations;
|
||||
}
|
||||
|
||||
// region getter
|
||||
@Override
|
||||
public long getTotalHits() {
|
||||
return totalHits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TotalHitsRelation getTotalHitsRelation() {
|
||||
return totalHitsRelation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getScrollId() {
|
||||
return scrollId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SearchHit<T>> getSearchHits() {
|
||||
return Collections.unmodifiableList(searchHits);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region SearchHit access
|
||||
@Override
|
||||
public SearchHit<T> getSearchHit(int index) {
|
||||
return searchHits.get(index);
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SearchHits{" + //
|
||||
"totalHits=" + totalHits + //
|
||||
", totalHitsRelation=" + totalHitsRelation + //
|
||||
", maxScore=" + maxScore + //
|
||||
", scrollId='" + scrollId + '\'' + //
|
||||
", searchHits={" + searchHits.size() + " elements}" + //
|
||||
", aggregations=" + aggregations + //
|
||||
'}';
|
||||
}
|
||||
|
||||
// region aggregations
|
||||
@Override
|
||||
@Nullable
|
||||
public Aggregations getAggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link SearchHitsIterator} encapsulates {@link SearchHit} results that can be wrapped in a Java 8
|
||||
* {@link java.util.stream.Stream}.
|
||||
*
|
||||
* @author Sascha Woo
|
||||
* @param <T>
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface SearchHitsIterator<T> extends CloseableIterator<SearchHit<T>> {
|
||||
|
||||
/**
|
||||
* @return the aggregations.
|
||||
*/
|
||||
@Nullable
|
||||
Aggregations getAggregations();
|
||||
|
||||
/**
|
||||
* @return the maximum score
|
||||
*/
|
||||
float getMaxScore();
|
||||
|
||||
/**
|
||||
* @return the number of total hits.
|
||||
*/
|
||||
long getTotalHits();
|
||||
|
||||
/**
|
||||
* @return the relation for the total hits
|
||||
*/
|
||||
TotalHitsRelation getTotalHitsRelation();
|
||||
|
||||
/**
|
||||
* @return true if aggregations are available
|
||||
*/
|
||||
default boolean hasAggregations() {
|
||||
return getAggregations() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import org.springframework.lang.Nullable;
|
||||
* APIs</a>.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface SearchOperations {
|
||||
@@ -50,6 +51,15 @@ public interface SearchOperations {
|
||||
return count(query, null, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* return number of elements found by given query
|
||||
*
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping and index name extraction
|
||||
* @return count
|
||||
*/
|
||||
long count(Query query, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* return number of elements found by given query
|
||||
*
|
||||
@@ -146,8 +156,9 @@ public interface SearchOperations {
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping
|
||||
* @param index the index to run the query against
|
||||
* @return a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of *
|
||||
* error.
|
||||
* @return a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed. The
|
||||
* try-with-resources construct should be used to ensure that the close method is invoked after the operations
|
||||
* are completed.
|
||||
* @deprecated since 4.0, use {@link #searchForStream(Query, Class, IndexCoordinates)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -211,50 +222,6 @@ public interface SearchOperations {
|
||||
return search(query, clazz, index).map(SearchHit::getId).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scrolled page for given query
|
||||
*
|
||||
* @param scrollTimeInMillis duration of the scroll time
|
||||
* @param query The search query.
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @param index the index to run the query against
|
||||
* @return scrolled page result
|
||||
* @deprecated since 4.0, use {@link #searchScrollStart(long, Query, Class, IndexCoordinates)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
IndexCoordinates index) {
|
||||
return (ScrolledPage<T>) SearchHitSupport
|
||||
.unwrapSearchHits(searchScrollStart(scrollTimeInMillis, query, clazz, index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next scrolled page.
|
||||
*
|
||||
* @param scrollId the scroll id
|
||||
* @param scrollTimeInMillis duration of the scroll time
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @return scrolled page result
|
||||
* @deprecated since 4.0, use {@link #searchScrollContinue(String, long, Class)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
default <T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz) {
|
||||
return (ScrolledPage<T>) SearchHitSupport
|
||||
.unwrapSearchHits(searchScrollContinue(scrollId, scrollTimeInMillis, clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the search contexts associated with specified scroll ids.
|
||||
*
|
||||
* @param scrollId the scroll id
|
||||
* @deprecated since 4.0, use {@link #searchScrollClear(String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default void clearScroll(String scrollId) {
|
||||
searchScrollClear(scrollId);
|
||||
}
|
||||
|
||||
/**
|
||||
* more like this query to search for documents that are "like" a specific document.
|
||||
*
|
||||
@@ -271,8 +238,41 @@ public interface SearchOperations {
|
||||
AggregatedPage<SearchHit<T>> aggregatedPage = SearchHitSupport.page(searchHits, query.getPageable());
|
||||
return (AggregatedPage<T>) SearchHitSupport.unwrapSearchHits(aggregatedPage);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param the entity class
|
||||
* @return the suggest response
|
||||
* @since 4.1
|
||||
*/
|
||||
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param index the index to run the query against
|
||||
* @return the suggest response
|
||||
*/
|
||||
SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute the query against elasticsearch and return the first returned object.
|
||||
*
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping and indexname extraction
|
||||
* @return the first found object
|
||||
*/
|
||||
@Nullable
|
||||
default <T> SearchHit<T> searchOne(Query query, Class<T> clazz) {
|
||||
List<SearchHit<T>> content = search(query, clazz).getSearchHits();
|
||||
return content.isEmpty() ? null : content.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query against elasticsearch and return the first returned object.
|
||||
*
|
||||
@@ -287,6 +287,17 @@ public interface SearchOperations {
|
||||
return content.isEmpty() ? null : content.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
* @param queries the queries to execute
|
||||
* @param clazz the entity clazz
|
||||
* @param <T> element return type
|
||||
* @return list of SearchHits
|
||||
* @since 4.1
|
||||
*/
|
||||
<T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
@@ -298,6 +309,16 @@ public interface SearchOperations {
|
||||
*/
|
||||
<T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
* @param queries the queries to execute
|
||||
* @param classes the entity classes
|
||||
* @return list of SearchHits
|
||||
* @since 4.1
|
||||
*/
|
||||
List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes);
|
||||
|
||||
/**
|
||||
* Execute the multi search query against elasticsearch and return result as {@link List} of {@link SearchHits}.
|
||||
*
|
||||
@@ -308,6 +329,16 @@ public interface SearchOperations {
|
||||
*/
|
||||
List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Execute the criteria query against elasticsearch and return result as {@link SearchHits}
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping and index name extraction
|
||||
* @return SearchHits containing the list of found objects
|
||||
*/
|
||||
<T> SearchHits<T> search(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Execute the criteria query against elasticsearch and return result as {@link SearchHits}
|
||||
*
|
||||
@@ -319,6 +350,16 @@ public interface SearchOperations {
|
||||
*/
|
||||
<T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* more like this query to search for documents that are "like" a specific document.
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping and index name extraction
|
||||
* @return SearchHits containing the list of found objects
|
||||
*/
|
||||
<T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* more like this query to search for documents that are "like" a specific document.
|
||||
*
|
||||
@@ -331,54 +372,29 @@ public interface SearchOperations {
|
||||
<T> SearchHits<T> search(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Returns scrolled page for given query
|
||||
* Executes the given {@link Query} against elasticsearch and return result as {@link SearchHitsIterator}.
|
||||
* <p>
|
||||
*
|
||||
* @param scrollTimeInMillis duration of the scroll time
|
||||
* @param query The search query.
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @param index the index to run the query against
|
||||
* @return scrolled page result
|
||||
* @param <T> element return type
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping and index name extraction
|
||||
* @return a {@link SearchHitsIterator} that wraps an Elasticsearch scroll context that needs to be closed. The
|
||||
* try-with-resources construct should be used to ensure that the close method is invoked after the operations
|
||||
* are completed.
|
||||
*/
|
||||
<T> ScrolledPage<SearchHit<T>> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||
IndexCoordinates index);
|
||||
<T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Returns next scrolled page
|
||||
*
|
||||
* @param scrollId the scroll id
|
||||
* @param scrollTimeInMillis duration of the scroll time
|
||||
* @param clazz The class of entity to retrieve.
|
||||
* @return scrolled page result
|
||||
*/
|
||||
<T> ScrolledPage<SearchHit<T>> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz);
|
||||
|
||||
/**
|
||||
* Clears the search contexts associated with specified scroll ids.
|
||||
*
|
||||
* @param scrollId the scroll id
|
||||
*/
|
||||
void searchScrollClear(String scrollId);
|
||||
|
||||
/**
|
||||
* Executes the given {@link Query} against elasticsearch and return result as {@link CloseableIterator}.
|
||||
* Executes the given {@link Query} against elasticsearch and return result as {@link SearchHitsIterator}.
|
||||
* <p>
|
||||
*
|
||||
* @param <T> element return type
|
||||
* @param query the query to execute
|
||||
* @param clazz the entity clazz used for property mapping
|
||||
* @param index the index to run the query against
|
||||
* @return a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of *
|
||||
* error.
|
||||
* @return a {@link SearchHitsIterator} that wraps an Elasticsearch scroll context that needs to be closed. The
|
||||
* try-with-resources construct should be used to ensure that the close method is invoked after the operations
|
||||
* are completed.
|
||||
*/
|
||||
<T> CloseableIterator<SearchHit<T>> searchForStream(Query query, Class<T> clazz, IndexCoordinates index);
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
*
|
||||
* @param suggestion the query
|
||||
* @param index the index to run the query against
|
||||
* @return the suggest response
|
||||
*/
|
||||
SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
||||
<T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Page definition for repositories that need to return a paged SearchHits.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface SearchPage<T> extends Page<SearchHit<T>> {
|
||||
SearchHits<T> getSearchHits();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public interface SearchScrollHits<T> extends SearchHits<T> {
|
||||
|
||||
/**
|
||||
* @return the scroll id
|
||||
*/
|
||||
String getScrollId();
|
||||
|
||||
}
|
||||
@@ -16,85 +16,112 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.data.elasticsearch.client.util.ScrollState;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to support streaming queries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Sascha Woo
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class StreamQueries {
|
||||
|
||||
/**
|
||||
* Stream query results using {@link ScrolledPage}.
|
||||
* Stream query results using {@link SearchScrollHits}.
|
||||
*
|
||||
* @param page the initial page.
|
||||
* @param continueFunction continuation function accepting the current scrollId.
|
||||
* @param clearScroll cleanup function accepting the current scrollId.
|
||||
* @param <T>
|
||||
* @return the {@link CloseableIterator}.
|
||||
* @param maxCount the maximum number of entities to return, a value of 0 means that all available entities are
|
||||
* returned
|
||||
* @param searchHits the initial hits
|
||||
* @param continueScrollFunction function to continue scrolling applies to the current scrollId.
|
||||
* @param clearScrollConsumer consumer to clear the scroll context by accepting the scrollIds to clear.
|
||||
* @param <T> the entity type
|
||||
* @return the {@link SearchHitsIterator}.
|
||||
*/
|
||||
static <T> CloseableIterator<T> streamResults(ScrolledPage<T> page,
|
||||
Function<String, ScrolledPage<T>> continueFunction, Consumer<String> clearScroll) {
|
||||
static <T> SearchHitsIterator<T> streamResults(int maxCount, SearchScrollHits<T> searchHits,
|
||||
Function<String, SearchScrollHits<T>> continueScrollFunction, Consumer<List<String>> clearScrollConsumer) {
|
||||
|
||||
return new CloseableIterator<T>() {
|
||||
Assert.notNull(searchHits, "searchHits must not be null.");
|
||||
Assert.notNull(searchHits.getScrollId(), "scrollId of searchHits must not be null.");
|
||||
Assert.notNull(continueScrollFunction, "continueScrollFunction must not be null.");
|
||||
Assert.notNull(clearScrollConsumer, "clearScrollConsumer must not be null.");
|
||||
|
||||
/** As we couldn't retrieve single result with scroll, store current hits. */
|
||||
private volatile Iterator<T> currentHits = page.iterator();
|
||||
Aggregations aggregations = searchHits.getAggregations();
|
||||
float maxScore = searchHits.getMaxScore();
|
||||
long totalHits = searchHits.getTotalHits();
|
||||
TotalHitsRelation totalHitsRelation = searchHits.getTotalHitsRelation();
|
||||
|
||||
/** The scroll id. */
|
||||
private volatile String scrollId = page.getScrollId();
|
||||
return new SearchHitsIterator<T>() {
|
||||
|
||||
/** If stream is finished (ie: cluster returns no results. */
|
||||
private volatile boolean finished = !currentHits.hasNext();
|
||||
private volatile AtomicInteger currentCount = new AtomicInteger();
|
||||
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
|
||||
private volatile boolean continueScroll = currentScrollHits.hasNext();
|
||||
private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId());
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
// Clear scroll on cluster only in case of error (cause elasticsearch auto clear scroll when it's done)
|
||||
if (!finished && scrollId != null && currentHits != null && currentHits.hasNext()) {
|
||||
clearScroll.accept(scrollId);
|
||||
}
|
||||
} finally {
|
||||
currentHits = null;
|
||||
scrollId = null;
|
||||
}
|
||||
clearScrollConsumer.accept(scrollState.getScrollIds());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Aggregations getAggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalHits() {
|
||||
return totalHits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TotalHitsRelation getTotalHitsRelation() {
|
||||
return totalHitsRelation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
// Test if stream is finished
|
||||
if (finished) {
|
||||
|
||||
if (!continueScroll || (maxCount > 0 && currentCount.get() >= maxCount)) {
|
||||
return false;
|
||||
}
|
||||
// Test if it remains hits
|
||||
if (currentHits == null || !currentHits.hasNext()) {
|
||||
// Do a new request
|
||||
ScrolledPage<T> scroll = continueFunction.apply(scrollId);
|
||||
// Save hits and scroll id
|
||||
currentHits = scroll.iterator();
|
||||
finished = !currentHits.hasNext();
|
||||
scrollId = scroll.getScrollId();
|
||||
|
||||
if (!currentScrollHits.hasNext()) {
|
||||
SearchScrollHits<T> nextPage = continueScrollFunction.apply(scrollState.getScrollId());
|
||||
currentScrollHits = nextPage.iterator();
|
||||
scrollState.updateScrollId(nextPage.getScrollId());
|
||||
continueScroll = currentScrollHits.hasNext();
|
||||
}
|
||||
return currentHits.hasNext();
|
||||
|
||||
return currentScrollHits.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
public SearchHit<T> next() {
|
||||
if (hasNext()) {
|
||||
return currentHits.next();
|
||||
currentCount.incrementAndGet();
|
||||
return currentScrollHits.next();
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("remove");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
/**
|
||||
* Enum to represent the relation that Elasticsearch returns for the totalHits value {@see <a href=
|
||||
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-request-body.html#request-body-search-track-total-hits">Ekasticsearch
|
||||
* docs</a>}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
*/
|
||||
public enum TotalHitsRelation {
|
||||
EQUAL_TO, //
|
||||
GREATER_THAN_OR_EQUAL_TO
|
||||
}
|
||||
+7
-6
@@ -40,7 +40,7 @@ public class AggregatedPageImpl<T> extends PageImpl<T> implements AggregatedPage
|
||||
@Nullable private String scrollId;
|
||||
private float maxScore;
|
||||
|
||||
private static Pageable pageableOrUnpaged(Pageable pageable) {
|
||||
private static Pageable pageableOrUnpaged(@Nullable Pageable pageable) {
|
||||
return ofNullable(pageable).orElse(Pageable.unpaged());
|
||||
}
|
||||
|
||||
@@ -82,24 +82,25 @@ public class AggregatedPageImpl<T> extends PageImpl<T> implements AggregatedPage
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, Aggregations aggregations) {
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, @Nullable Aggregations aggregations) {
|
||||
super(content, pageableOrUnpaged(pageable), total);
|
||||
this.aggregations = aggregations;
|
||||
}
|
||||
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, Aggregations aggregations, float maxScore) {
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, @Nullable Aggregations aggregations,
|
||||
float maxScore) {
|
||||
this(content, pageableOrUnpaged(pageable), total, aggregations);
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, Aggregations aggregations,
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, @Nullable Aggregations aggregations,
|
||||
String scrollId) {
|
||||
this(content, pageableOrUnpaged(pageable), total, aggregations);
|
||||
this.scrollId = scrollId;
|
||||
}
|
||||
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, Aggregations aggregations, String scrollId,
|
||||
float maxScore) {
|
||||
public AggregatedPageImpl(List<T> content, Pageable pageable, long total, @Nullable Aggregations aggregations,
|
||||
String scrollId, float maxScore) {
|
||||
this(content, pageableOrUnpaged(pageable), total, aggregations, scrollId);
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
+36
-10
@@ -15,15 +15,41 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.client.support;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@Data
|
||||
public class AliasData {
|
||||
String filter = null;
|
||||
String routing = null;
|
||||
String search_routing = null;
|
||||
String index_routing = null;
|
||||
private String filter = null;
|
||||
private String routing = null;
|
||||
private String search_routing = null;
|
||||
private String index_routing = null;
|
||||
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void setFilter(String filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public String getRouting() {
|
||||
return routing;
|
||||
}
|
||||
|
||||
public void setRouting(String routing) {
|
||||
this.routing = routing;
|
||||
}
|
||||
|
||||
public String getSearch_routing() {
|
||||
return search_routing;
|
||||
}
|
||||
|
||||
public void setSearch_routing(String search_routing) {
|
||||
this.search_routing = search_routing;
|
||||
}
|
||||
|
||||
public String getIndex_routing() {
|
||||
return index_routing;
|
||||
}
|
||||
|
||||
public void setIndex_routing(String index_routing) {
|
||||
this.index_routing = index_routing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.elasticsearch.core.completion;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,11 @@ public class Completion {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
public Completion(List<String> input) {
|
||||
this.input = input.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public String[] getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
+7
-63
@@ -15,17 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.convert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
@@ -39,6 +30,8 @@ import org.springframework.util.Assert;
|
||||
* @author Mohsin Husen
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sasch Woo
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
public interface ElasticsearchConverter
|
||||
extends EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Document> {
|
||||
@@ -53,59 +46,6 @@ public interface ElasticsearchConverter
|
||||
return new SpelAwareProxyProjectionFactory();
|
||||
}
|
||||
|
||||
// region read
|
||||
/**
|
||||
* Map a single {@link Document} to an instance of the given type.
|
||||
*
|
||||
* @param document the document to map
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T> the class of type
|
||||
* @return can be {@literal null} if the document is null or {@link Document#isEmpty()} is true.
|
||||
* @since 4.0
|
||||
*/
|
||||
@Nullable
|
||||
<T> T mapDocument(@Nullable Document document, Class<T> type);
|
||||
|
||||
/**
|
||||
* Map a list of {@link Document}s to a list of instance of the given type.
|
||||
*
|
||||
* @param documents must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T> the class of type
|
||||
* @return a list obtained by calling {@link #mapDocument(Document, Class)} on the elements of the list.
|
||||
* @since 4.0
|
||||
*/
|
||||
default <T> List<T> mapDocuments(List<Document> documents, Class<T> type) {
|
||||
return documents.stream().map(document -> mapDocument(document, type)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* builds a {@link SearchHits} from a {@link SearchDocumentResponse}.
|
||||
*
|
||||
* @param <T> the clazz of the type, must not be {@literal null}.
|
||||
* @param type the type of the returned data, must not be {@literal null}.
|
||||
* @param searchDocumentResponse the response to read from, must not be {@literal null}.
|
||||
* @return a SearchHits object
|
||||
* @since 4.0
|
||||
*/
|
||||
<T> SearchHits<T> read(Class<T> type, SearchDocumentResponse searchDocumentResponse);
|
||||
|
||||
/**
|
||||
* builds a {@link SearchHit} from a {@link SearchDocument}.
|
||||
*
|
||||
* @param <T> the clazz of the type, must not be {@literal null}.
|
||||
* @param type the type of the returned data, must not be {@literal null}.
|
||||
* @param searchDocument must not be {@literal null}
|
||||
* @return SearchHit with all available information filled in
|
||||
* @since 4.0
|
||||
*/
|
||||
<T> SearchHit<T> read(Class<T> type, SearchDocument searchDocument);
|
||||
|
||||
<T> AggregatedPage<SearchHit<T>> mapResults(SearchDocumentResponse response, Class<T> clazz,
|
||||
@Nullable Pageable pageable);
|
||||
|
||||
// endregion
|
||||
|
||||
// region write
|
||||
/**
|
||||
* Convert a given {@literal idValue} to its {@link String} representation taking potentially registered
|
||||
@@ -133,8 +73,12 @@ public interface ElasticsearchConverter
|
||||
* @return will not be {@literal null}.
|
||||
*/
|
||||
default Document mapObject(@Nullable Object source) {
|
||||
|
||||
Document target = Document.create();
|
||||
write(source, target);
|
||||
|
||||
if (source != null) {
|
||||
write(source, target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
// endregion
|
||||
|
||||
+35
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core.convert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -48,6 +49,8 @@ public class ElasticsearchCustomConversions extends CustomConversions {
|
||||
converters.add(UUIDToStringConverter.INSTANCE);
|
||||
converters.add(BigDecimalToDoubleConverter.INSTANCE);
|
||||
converters.add(DoubleToBigDecimalConverter.INSTANCE);
|
||||
converters.add(ByteArrayToBase64Converter.INSTANCE);
|
||||
converters.add(Base64ToByteArrayConverter.INSTANCE);
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(ElasticsearchSimpleTypes.HOLDER, STORE_CONVERTERS);
|
||||
@@ -117,4 +120,36 @@ public class ElasticsearchCustomConversions extends CustomConversions {
|
||||
return NumberUtils.convertNumberToTargetClass(source, Double.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to write a byte[] to a base64 encoded {@link String} value.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@WritingConverter
|
||||
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
|
||||
|
||||
INSTANCE,;
|
||||
|
||||
@Override
|
||||
public String convert(byte[] source) {
|
||||
return Base64.getEncoder().encodeToString(source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} to read a byte[] from a base64 encoded {@link String} value.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@ReadingConverter
|
||||
enum Base64ToByteArrayConverter implements Converter<String, byte[]> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] convert(String source) {
|
||||
return Base64.getDecoder().decode(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -17,7 +17,9 @@ package org.springframework.data.elasticsearch.core.convert;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.elasticsearch.common.time.DateFormatter;
|
||||
@@ -73,9 +75,25 @@ final public class ElasticsearchDateConverter {
|
||||
* @return the formatted object
|
||||
*/
|
||||
public String format(TemporalAccessor accessor) {
|
||||
|
||||
Assert.notNull(accessor, "accessor must not be null");
|
||||
|
||||
return dateFormatter.format(accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the given {@link TemporalAccessor} int a String
|
||||
*
|
||||
* @param date must not be {@literal null}
|
||||
* @return the formatted object
|
||||
*/
|
||||
public String format(Date date) {
|
||||
|
||||
Assert.notNull(date, "accessor must not be null");
|
||||
|
||||
return dateFormatter.format(Instant.ofEpochMilli(date.getTime()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String into an object
|
||||
*
|
||||
@@ -96,4 +114,14 @@ final public class ElasticsearchDateConverter {
|
||||
throw new ConversionException("could not create object of class " + type.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a String into a Date.
|
||||
*
|
||||
* @param input the String to parse, must not be {@literal null}.
|
||||
* @return the new created object
|
||||
*/
|
||||
public Date parse(String input) {
|
||||
return new Date(Instant.from(dateFormatter.parse(input)).toEpochMilli());
|
||||
}
|
||||
}
|
||||
|
||||
+108
-115
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.convert;
|
||||
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -26,9 +27,10 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -38,25 +40,20 @@ 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.EntityInstantiator;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.annotations.ScriptedField;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
|
||||
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
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.EntityInstantiator;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.PropertyValueProvider;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -77,18 +74,24 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Konrad Kurdej
|
||||
* @since 3.2
|
||||
*/
|
||||
public class MappingElasticsearchConverter
|
||||
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MappingElasticsearchConverter.class);
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
private final GenericConversionService conversionService;
|
||||
|
||||
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
||||
private EntityInstantiators instantiators = new EntityInstantiators();
|
||||
private final EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
private ElasticsearchTypeMapper typeMapper;
|
||||
private final ElasticsearchTypeMapper typeMapper;
|
||||
|
||||
private ConcurrentHashMap<String, Integer> propertyWarnings = new ConcurrentHashMap<>();
|
||||
|
||||
public MappingElasticsearchConverter(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
@@ -108,6 +111,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
if (mappingContext instanceof ApplicationContextAware) {
|
||||
((ApplicationContextAware) mappingContext).setApplicationContext(applicationContext);
|
||||
}
|
||||
@@ -145,85 +149,6 @@ public class MappingElasticsearchConverter
|
||||
|
||||
// region read
|
||||
|
||||
@Override
|
||||
public <T> AggregatedPage<SearchHit<T>> mapResults(SearchDocumentResponse response, Class<T> type,
|
||||
@Nullable Pageable pageable) {
|
||||
|
||||
List<SearchHit<T>> results = response.getSearchDocuments().stream() //
|
||||
.map(searchDocument -> read(type, searchDocument)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new AggregatedPageImpl<>(results, pageable, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SearchHits<T> read(Class<T> type, SearchDocumentResponse searchDocumentResponse) {
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
Assert.notNull(searchDocumentResponse, "searchDocumentResponse must not be null");
|
||||
|
||||
long totalHits = searchDocumentResponse.getTotalHits();
|
||||
float maxScore = searchDocumentResponse.getMaxScore();
|
||||
String scrollId = searchDocumentResponse.getScrollId();
|
||||
List<SearchHit<T>> searchHits = searchDocumentResponse.getSearchDocuments().stream() //
|
||||
.map(searchDocument -> read(type, searchDocument)) //
|
||||
.collect(Collectors.toList());
|
||||
Aggregations aggregations = searchDocumentResponse.getAggregations();
|
||||
SearchHits.TotalHitsRelation totalHitsRelation = SearchHits.TotalHitsRelation
|
||||
.valueOf(searchDocumentResponse.getTotalHitsRelation());
|
||||
|
||||
return new SearchHits<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SearchHit<T> read(Class<T> type, SearchDocument searchDocument) {
|
||||
|
||||
Assert.notNull(type, "type must not be null");
|
||||
Assert.notNull(searchDocument, "searchDocument must not be null");
|
||||
|
||||
String id = searchDocument.hasId() ? searchDocument.getId() : null;
|
||||
float score = searchDocument.getScore();
|
||||
Object[] sortValues = searchDocument.getSortValues();
|
||||
Map<String, List<String>> highlightFields = getHighlightsAndRemapFieldNames(type, searchDocument);
|
||||
T content = mapDocument(searchDocument, type);
|
||||
|
||||
return new SearchHit<T>(id, score, sortValues, highlightFields, content);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<String, List<String>> getHighlightsAndRemapFieldNames(Class<?> type, SearchDocument searchDocument) {
|
||||
Map<String, List<String>> highlightFields = searchDocument.getHighlightFields();
|
||||
|
||||
if (highlightFields == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
|
||||
if (persistentEntity == null) {
|
||||
return highlightFields;
|
||||
}
|
||||
|
||||
return highlightFields.entrySet().stream().collect(Collectors.toMap(entry -> {
|
||||
ElasticsearchPersistentProperty property = persistentEntity.getPersistentPropertyWithFieldName(entry.getKey());
|
||||
return property != null ? property.getName() : entry.getKey();
|
||||
}, Entry::getValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T mapDocument(@Nullable Document document, Class<T> type) {
|
||||
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T mappedResult = read(type, document);
|
||||
|
||||
return type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)
|
||||
? getProjectionFactory().createProjection(type, mappedResult)
|
||||
: type.cast(mappedResult);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Document source) {
|
||||
@@ -289,6 +214,14 @@ public class MappingElasticsearchConverter
|
||||
targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
|
||||
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
|
||||
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
|
||||
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
|
||||
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (source instanceof SearchDocument) {
|
||||
@@ -304,6 +237,14 @@ public class MappingElasticsearchConverter
|
||||
|
||||
}
|
||||
|
||||
private boolean isAssignedSeqNo(long seqNo) {
|
||||
return seqNo >= 0;
|
||||
}
|
||||
|
||||
private boolean isAssignedPrimaryTerm(long primaryTerm) {
|
||||
return primaryTerm > 0;
|
||||
}
|
||||
|
||||
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
|
||||
ElasticsearchPropertyValueProvider valueProvider) {
|
||||
|
||||
@@ -312,7 +253,7 @@ public class MappingElasticsearchConverter
|
||||
|
||||
for (ElasticsearchPersistentProperty prop : entity) {
|
||||
|
||||
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
|
||||
if (entity.isConstructorArgument(prop) || prop.isScoreProperty() || !prop.isReadable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -334,11 +275,26 @@ public class MappingElasticsearchConverter
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<R> rawType = targetType.getType();
|
||||
|
||||
if (property.hasPropertyConverter() && String.class.isAssignableFrom(source.getClass())) {
|
||||
source = property.getPropertyConverter().read((String) source);
|
||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
||||
&& !conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
|
||||
// log at most 5 times
|
||||
String propertyName = property.getOwner().getType().getSimpleName() + '.' + property.getName();
|
||||
String key = propertyName + "-read";
|
||||
int count = propertyWarnings.computeIfAbsent(key, k -> 0);
|
||||
if (count < 5) {
|
||||
LOGGER.warn(
|
||||
"Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for reading!"
|
||||
+ " It cannot be mapped from a complex object in Elasticsearch!",
|
||||
property.getType().getSimpleName(), propertyName);
|
||||
propertyWarnings.put(key, count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Class<R> rawType = targetType.getType();
|
||||
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
return rawType.cast(conversionService.convert(source, rawType));
|
||||
} else if (source instanceof List) {
|
||||
@@ -360,16 +316,24 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
Collection<Object> target = createCollectionForValue(targetType, source.size());
|
||||
TypeInformation<?> componentType = targetType.getComponentType();
|
||||
|
||||
for (Object value : source) {
|
||||
|
||||
if (isSimpleType(value)) {
|
||||
target.add(
|
||||
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
|
||||
if (value == null) {
|
||||
target.add(null);
|
||||
} else if (componentType != null && !ClassTypeInformation.OBJECT.equals(componentType)
|
||||
&& isSimpleType(componentType.getType())) {
|
||||
target.add(readSimpleValue(value, componentType));
|
||||
} else if (isSimpleType(value)) {
|
||||
target.add(readSimpleValue(value, componentType != null ? componentType : targetType));
|
||||
} else {
|
||||
|
||||
if (value instanceof List) {
|
||||
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
|
||||
} else if (value instanceof Map) {
|
||||
target
|
||||
.add(readMapValue((Map<String, Object>) value, property, property.getTypeInformation().getActualType()));
|
||||
} else {
|
||||
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map<String, Object>) value));
|
||||
}
|
||||
@@ -395,27 +359,31 @@ public class MappingElasticsearchConverter
|
||||
Map<String, Object> target = new LinkedHashMap<>();
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
|
||||
if (isSimpleType(entry.getValue())) {
|
||||
target.put(entry.getKey(),
|
||||
readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType));
|
||||
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.getMapValueType() : targetType));
|
||||
} else {
|
||||
|
||||
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entry.getValue());
|
||||
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entryValue);
|
||||
|
||||
if (targetEntity.getTypeInformation().isMap()) {
|
||||
|
||||
Map<String, Object> valueMap = (Map<String, Object>) entry.getValue();
|
||||
Map<String, Object> valueMap = (Map<String, Object>) entryValue;
|
||||
if (typeMapper.containsTypeInformation(valueMap)) {
|
||||
target.put(entry.getKey(), readEntity(targetEntity, valueMap));
|
||||
target.put(entryKey, readEntity(targetEntity, valueMap));
|
||||
} else {
|
||||
target.put(entry.getKey(), readValue(valueMap, property, targetEntity.getTypeInformation()));
|
||||
target.put(entryKey, readValue(valueMap, property, targetEntity.getTypeInformation()));
|
||||
}
|
||||
|
||||
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
|
||||
target.put(entry.getKey(),
|
||||
readValue(entry.getValue(), property, targetEntity.getTypeInformation().getActualType()));
|
||||
target.put(entryKey, readValue(entryValue, property, targetEntity.getTypeInformation().getActualType()));
|
||||
} else {
|
||||
target.put(entry.getKey(), readEntity(targetEntity, (Map<String, Object>) entry.getValue()));
|
||||
target.put(entryKey, readEntity(targetEntity, (Map<String, Object>) entryValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -531,6 +499,20 @@ public class MappingElasticsearchConverter
|
||||
if (property.hasPropertyConverter()) {
|
||||
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
|
||||
value = propertyConverter.write(value);
|
||||
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
|
||||
&& !conversions.hasCustomWriteTarget(value.getClass())) {
|
||||
|
||||
// log at most 5 times
|
||||
String propertyName = entity.getType().getSimpleName() + '.' + property.getName();
|
||||
String key = propertyName + "-write";
|
||||
int count = propertyWarnings.computeIfAbsent(key, k -> 0);
|
||||
if (count < 5) {
|
||||
LOGGER.warn(
|
||||
"Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing!"
|
||||
+ " It will be mapped to a complex object in Elasticsearch!",
|
||||
property.getType().getSimpleName(), propertyName);
|
||||
propertyWarnings.put(key, count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSimpleType(value)) {
|
||||
@@ -614,9 +596,18 @@ public class MappingElasticsearchConverter
|
||||
Map<Object, Object> target = new LinkedHashMap<>();
|
||||
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
|
||||
|
||||
if (!typeHint.getActualType().getType().equals(Object.class)
|
||||
TypeInformation<?> actualType = typeHint.getActualType();
|
||||
|
||||
if (actualType != null && !actualType.getType().equals(Object.class)
|
||||
&& isSimpleType(typeHint.getMapValueType().getType())) {
|
||||
mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue())));
|
||||
mapSource.forEach(it -> {
|
||||
|
||||
if (it.getValue() == null) {
|
||||
target.put(it.getKey(), null);
|
||||
} else {
|
||||
target.put(it.getKey(), getWriteSimpleValue(it.getValue()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
mapSource.forEach(it -> {
|
||||
@@ -646,14 +637,20 @@ public class MappingElasticsearchConverter
|
||||
: Streamable.of(ObjectUtils.toObjectArray(value));
|
||||
|
||||
List<Object> target = new ArrayList<>();
|
||||
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
|
||||
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
|
||||
TypeInformation<?> actualType = typeHint.getActualType();
|
||||
Class<?> type = actualType != null ? actualType.getType() : null;
|
||||
|
||||
if (type != null && !type.equals(Object.class) && isSimpleType(type)) {
|
||||
// noinspection ReturnOfNull
|
||||
collectionSource //
|
||||
.map(element -> element != null ? getWriteSimpleValue(element) : null) //
|
||||
.forEach(target::add);
|
||||
} else {
|
||||
|
||||
collectionSource.map(it -> {
|
||||
|
||||
if (it == null) {
|
||||
//noinspection ReturnOfNull
|
||||
// noinspection ReturnOfNull
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -721,10 +718,6 @@ public class MappingElasticsearchConverter
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface Document extends Map<String, Object> {
|
||||
@@ -58,7 +59,7 @@ public interface Document extends Map<String, Object> {
|
||||
* @param map source map containing key-value pairs and sub-documents. must not be {@literal null}.
|
||||
* @return a new {@link Document}.
|
||||
*/
|
||||
static Document from(Map<String, Object> map) {
|
||||
static Document from(Map<String, ? extends Object> map) {
|
||||
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
|
||||
@@ -110,6 +111,27 @@ public interface Document extends Map<String, Object> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index if this document was retrieved from an index
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default String getIndex() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index name for this document
|
||||
*
|
||||
* @param index index name
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}.
|
||||
* @since 4.1
|
||||
*/
|
||||
default void setIndex(@Nullable String index) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the identifier associated with this {@link Document}.
|
||||
* <p>
|
||||
@@ -165,6 +187,70 @@ public interface Document extends Map<String, Object> {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@literal true} if this {@link Document} is associated with a seq_no.
|
||||
*
|
||||
* @return {@literal true} if this {@link Document} is associated with a seq_no, {@literal false} otherwise.
|
||||
*/
|
||||
default boolean hasSeqNo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the seq_no associated with this {@link Document}.
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}. It's recommended to check
|
||||
* {@link #hasSeqNo()} prior to calling this method.
|
||||
*
|
||||
* @return the seq_no associated with this {@link Document}.
|
||||
* @throws IllegalStateException if the underlying implementation supports seq_no's but no seq_no was yet associated
|
||||
* with the document.
|
||||
*/
|
||||
default long getSeqNo() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the seq_no for this {@link Document}.
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}.
|
||||
*/
|
||||
default void setSeqNo(long seqNo) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@literal true} if this {@link Document} is associated with a primary_term.
|
||||
*
|
||||
* @return {@literal true} if this {@link Document} is associated with a primary_term, {@literal false} otherwise.
|
||||
*/
|
||||
default boolean hasPrimaryTerm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary_term associated with this {@link Document}.
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}. It's recommended to check
|
||||
* {@link #hasPrimaryTerm()} prior to calling this method.
|
||||
*
|
||||
* @return the primary_term associated with this {@link Document}.
|
||||
* @throws IllegalStateException if the underlying implementation supports primary_term's but no primary_term was yet
|
||||
* associated with the document.
|
||||
*/
|
||||
default long getPrimaryTerm() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the primary_term for this {@link Document}.
|
||||
* <p>
|
||||
* The default implementation throws {@link UnsupportedOperationException}.
|
||||
*/
|
||||
default void setPrimaryTerm(long primaryTerm) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no
|
||||
* mapping for the key. The value is casted within the method which makes it useful for calling code as it does not
|
||||
@@ -396,4 +482,5 @@ public interface Document extends Map<String, Object> {
|
||||
* @return a JSON representation of this document.
|
||||
*/
|
||||
String toJson();
|
||||
|
||||
}
|
||||
|
||||
+152
-211
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -37,6 +38,7 @@ import org.elasticsearch.common.document.DocumentField;
|
||||
import org.elasticsearch.common.text.Text;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.springframework.data.elasticsearch.ElasticsearchException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -54,6 +56,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
public class DocumentAdapters {
|
||||
@@ -76,12 +79,16 @@ public class DocumentAdapters {
|
||||
}
|
||||
|
||||
if (source.isSourceEmpty()) {
|
||||
return fromDocumentFields(source, source.getId(), source.getVersion());
|
||||
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
source.getPrimaryTerm());
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSourceAsMap());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
document.setVersion(source.getVersion());
|
||||
document.setSeqNo(source.getSeqNo());
|
||||
document.setPrimaryTerm(source.getPrimaryTerm());
|
||||
|
||||
return document;
|
||||
}
|
||||
@@ -104,12 +111,16 @@ public class DocumentAdapters {
|
||||
}
|
||||
|
||||
if (source.isSourceEmpty()) {
|
||||
return fromDocumentFields(source, source.getId(), source.getVersion());
|
||||
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
|
||||
source.getPrimaryTerm());
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSource());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
document.setVersion(source.getVersion());
|
||||
document.setSeqNo(source.getSeqNo());
|
||||
document.setPrimaryTerm(source.getPrimaryTerm());
|
||||
|
||||
return document;
|
||||
}
|
||||
@@ -118,16 +129,16 @@ public class DocumentAdapters {
|
||||
* Creates a List of {@link Document}s from {@link MultiGetResponse}.
|
||||
*
|
||||
* @param source the source {@link MultiGetResponse}, not {@literal null}.
|
||||
* @return a possibly empty list of the Documents.
|
||||
* @return a list of Documents, contains null values for not found Documents.
|
||||
*/
|
||||
public static List<Document> from(MultiGetResponse source) {
|
||||
|
||||
Assert.notNull(source, "MultiGetResponse must not be null");
|
||||
|
||||
//noinspection ReturnOfNull
|
||||
// noinspection ReturnOfNull
|
||||
return Arrays.stream(source.getResponses()) //
|
||||
.map(itemResponse -> itemResponse.isFailed() ? null : DocumentAdapters.from(itemResponse.getResponse())) //
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,34 +157,68 @@ public class DocumentAdapters {
|
||||
.collect(Collectors.toMap(Map.Entry::getKey,
|
||||
entry -> Arrays.stream(entry.getValue().getFragments()).map(Text::string).collect(Collectors.toList()))));
|
||||
|
||||
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
|
||||
|
||||
if (sourceInnerHits != null) {
|
||||
sourceInnerHits.forEach((name, searchHits) -> {
|
||||
innerHits.put(name, SearchDocumentResponse.from(searchHits, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
NestedMetaData nestedMetaData = null;
|
||||
|
||||
if (source.getNestedIdentity() != null) {
|
||||
nestedMetaData = from(source.getNestedIdentity());
|
||||
}
|
||||
|
||||
BytesReference sourceRef = source.getSourceRef();
|
||||
|
||||
if (sourceRef == null || sourceRef.length() == 0) {
|
||||
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
|
||||
fromDocumentFields(source, source.getId(), source.getVersion()));
|
||||
return new SearchDocumentAdapter(
|
||||
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
|
||||
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
|
||||
innerHits, nestedMetaData);
|
||||
}
|
||||
|
||||
Document document = Document.from(source.getSourceAsMap());
|
||||
document.setIndex(source.getIndex());
|
||||
document.setId(source.getId());
|
||||
|
||||
if (source.getVersion() >= 0) {
|
||||
document.setVersion(source.getVersion());
|
||||
}
|
||||
document.setSeqNo(source.getSeqNo());
|
||||
document.setPrimaryTerm(source.getPrimaryTerm());
|
||||
|
||||
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
|
||||
document);
|
||||
document, innerHits, nestedMetaData);
|
||||
}
|
||||
|
||||
private static NestedMetaData from(SearchHit.NestedIdentity nestedIdentity) {
|
||||
|
||||
NestedMetaData child = null;
|
||||
|
||||
if (nestedIdentity.getChild() != null) {
|
||||
child = from(nestedIdentity.getChild());
|
||||
}
|
||||
|
||||
return NestedMetaData.of(nestedIdentity.getField().string(), nestedIdentity.getOffset(), child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an unmodifiable {@link Document} from {@link Iterable} of {@link DocumentField}s.
|
||||
*
|
||||
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
|
||||
* @param index
|
||||
* @return the adapted {@link Document}.
|
||||
*/
|
||||
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String id, long version) {
|
||||
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
|
||||
long version, long seqNo, long primaryTerm) {
|
||||
|
||||
if (documentFields instanceof Collection) {
|
||||
return new DocumentFieldAdapter((Collection<DocumentField>) documentFields, id, version);
|
||||
return new DocumentFieldAdapter((Collection<DocumentField>) documentFields, index, id, version, seqNo,
|
||||
primaryTerm);
|
||||
}
|
||||
|
||||
List<DocumentField> fields = new ArrayList<>();
|
||||
@@ -181,53 +226,49 @@ public class DocumentAdapters {
|
||||
fields.add(documentField);
|
||||
}
|
||||
|
||||
return new DocumentFieldAdapter(fields, id, version);
|
||||
return new DocumentFieldAdapter(fields, index, id, version, seqNo, primaryTerm);
|
||||
}
|
||||
|
||||
// TODO: Performance regarding keys/values/entry-set
|
||||
static class DocumentFieldAdapter implements Document {
|
||||
|
||||
private final Collection<DocumentField> documentFields;
|
||||
private final String index;
|
||||
private final String id;
|
||||
private final long version;
|
||||
private final long seqNo;
|
||||
private final long primaryTerm;
|
||||
|
||||
DocumentFieldAdapter(Collection<DocumentField> documentFields, String id, long version) {
|
||||
DocumentFieldAdapter(Collection<DocumentField> documentFields, String index, String id, long version, long seqNo,
|
||||
long primaryTerm) {
|
||||
this.documentFields = documentFields;
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.seqNo = seqNo;
|
||||
this.primaryTerm = primaryTerm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasId() {
|
||||
return StringUtils.hasLength(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getId()
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasVersion()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasVersion() {
|
||||
return this.version >= 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getVersion()
|
||||
*/
|
||||
@Override
|
||||
public long getVersion() {
|
||||
|
||||
@@ -238,28 +279,46 @@ public class DocumentAdapters {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSeqNo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeqNo() {
|
||||
|
||||
if (!hasSeqNo()) {
|
||||
throw new IllegalStateException("No seq_no associated with this Document");
|
||||
}
|
||||
|
||||
return this.seqNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrimaryTerm() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPrimaryTerm() {
|
||||
|
||||
if (!hasPrimaryTerm()) {
|
||||
throw new IllegalStateException("No primary_term associated with this Document");
|
||||
}
|
||||
|
||||
return this.primaryTerm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return documentFields.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return documentFields.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsKey(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
|
||||
@@ -272,10 +331,6 @@ public class DocumentAdapters {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsValue(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
|
||||
@@ -290,89 +345,52 @@ public class DocumentAdapters {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object get(Object key) {
|
||||
return documentFields.stream() //
|
||||
.filter(documentField -> documentField.getName().equals(key)) //
|
||||
.map(DocumentField::getValue)
|
||||
.findFirst() //
|
||||
.map(DocumentField::getValue).findFirst() //
|
||||
.orElse(null); //
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#putAll(Map)
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#keySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return documentFields.stream().map(DocumentField::getName).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#values()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return documentFields.stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#entrySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return documentFields.stream().collect(Collectors.toMap(DocumentField::getName, DocumentFieldAdapter::getValue))
|
||||
.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#forEach(java.util.function.BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super Object> action) {
|
||||
|
||||
@@ -381,10 +399,6 @@ public class DocumentAdapters {
|
||||
documentFields.forEach(field -> action.accept(field.getName(), getValue(field)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#toJson()
|
||||
*/
|
||||
@Override
|
||||
public String toJson() {
|
||||
|
||||
@@ -412,10 +426,6 @@ public class DocumentAdapters {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson();
|
||||
@@ -446,21 +456,22 @@ public class DocumentAdapters {
|
||||
private final Map<String, List<Object>> fields = new HashMap<>();
|
||||
private final Document delegate;
|
||||
private final Map<String, List<String>> highlightFields = new HashMap<>();
|
||||
private final Map<String, SearchDocumentResponse> innerHits = new HashMap<>();
|
||||
@Nullable private final NestedMetaData nestedMetaData;
|
||||
|
||||
SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields,
|
||||
Map<String, List<String>> highlightFields, Document delegate) {
|
||||
Map<String, List<String>> highlightFields, Document delegate, Map<String, SearchDocumentResponse> innerHits,
|
||||
@Nullable NestedMetaData nestedMetaData) {
|
||||
|
||||
this.score = score;
|
||||
this.sortValues = sortValues;
|
||||
this.delegate = delegate;
|
||||
fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues()));
|
||||
this.highlightFields.putAll(highlightFields);
|
||||
this.innerHits.putAll(innerHits);
|
||||
this.nestedMetaData = nestedMetaData;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#append(java.lang.String, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public SearchDocument append(String key, Object value) {
|
||||
delegate.append(key, value);
|
||||
@@ -468,227 +479,173 @@ public class DocumentAdapters {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getScore()
|
||||
*/
|
||||
@Override
|
||||
public float getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getFields()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<Object>> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getSortValues()
|
||||
*/
|
||||
@Override
|
||||
public Object[] getSortValues() {
|
||||
return sortValues;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.SearchDocument#getHighlightFields()
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<String>> getHighlightFields() {
|
||||
return highlightFields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
*/
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return delegate.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasId() {
|
||||
return delegate.hasId();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getId()
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return delegate.getId();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setId(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
delegate.setId(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasVersion()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasVersion() {
|
||||
return delegate.hasVersion();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getVersion()
|
||||
*/
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return delegate.getVersion();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setVersion(long)
|
||||
*/
|
||||
@Override
|
||||
public void setVersion(long version) {
|
||||
delegate.setVersion(version);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#get(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSeqNo() {
|
||||
return delegate.hasSeqNo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeqNo() {
|
||||
return delegate.getSeqNo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeqNo(long seqNo) {
|
||||
delegate.setSeqNo(seqNo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrimaryTerm() {
|
||||
return delegate.hasPrimaryTerm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPrimaryTerm() {
|
||||
return delegate.getPrimaryTerm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryTerm(long primaryTerm) {
|
||||
delegate.setPrimaryTerm(primaryTerm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, SearchDocumentResponse> getInnerHits() {
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public NestedMetaData getNestedMetaData() {
|
||||
return nestedMetaData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(Object key, Class<T> type) {
|
||||
return delegate.get(key, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#toJson()
|
||||
*/
|
||||
@Override
|
||||
public String toJson() {
|
||||
return delegate.toJson();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsKey(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return delegate.containsKey(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsValue(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return delegate.containsValue(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#putAll(Map)
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
delegate.putAll(m);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#keySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#values()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return delegate.values();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#entrySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -701,37 +658,21 @@ public class DocumentAdapters {
|
||||
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#forEach(java.util.function.BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super Object> action) {
|
||||
delegate.forEach(action);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
return delegate.remove(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
|
||||
+78
-1
@@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* {@link Document} implementation backed by a {@link LinkedHashMap}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
class MapDocument implements Document {
|
||||
@@ -39,17 +40,31 @@ class MapDocument implements Document {
|
||||
|
||||
private final LinkedHashMap<String, Object> documentAsMap;
|
||||
|
||||
private @Nullable String index;
|
||||
private @Nullable String id;
|
||||
private @Nullable Long version;
|
||||
private @Nullable Long seqNo;
|
||||
private @Nullable Long primaryTerm;
|
||||
|
||||
MapDocument() {
|
||||
this(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
MapDocument(Map<String, Object> documentAsMap) {
|
||||
MapDocument(Map<String, ? extends Object> documentAsMap) {
|
||||
this.documentAsMap = new LinkedHashMap<>(documentAsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndex(@Nullable String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasId()
|
||||
@@ -114,6 +129,68 @@ class MapDocument implements Document {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSeqNo() {
|
||||
return this.seqNo != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getSeqNo()
|
||||
*/
|
||||
@Override
|
||||
public long getSeqNo() {
|
||||
|
||||
if (!hasSeqNo()) {
|
||||
throw new IllegalStateException("No seq_no associated with this Document");
|
||||
}
|
||||
|
||||
return this.seqNo;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setSeqNo()
|
||||
*/
|
||||
public void setSeqNo(long seqNo) {
|
||||
this.seqNo = seqNo;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#hasPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPrimaryTerm() {
|
||||
return this.primaryTerm != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#getPrimaryTerm()
|
||||
*/
|
||||
@Override
|
||||
public long getPrimaryTerm() {
|
||||
|
||||
if (!hasPrimaryTerm()) {
|
||||
throw new IllegalStateException("No primary_term associated with this Document");
|
||||
}
|
||||
|
||||
return this.primaryTerm;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.document.Document#setPrimaryTerm()
|
||||
*/
|
||||
public void setPrimaryTerm(long primaryTerm) {
|
||||
this.primaryTerm = primaryTerm;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.document;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* meta data returned for nested inner hits.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class NestedMetaData {
|
||||
|
||||
private final String field;
|
||||
private final int offset;
|
||||
@Nullable private final NestedMetaData child;
|
||||
|
||||
public static NestedMetaData of(String field, int offset, @Nullable NestedMetaData nested) {
|
||||
return new NestedMetaData(field, offset, nested);
|
||||
}
|
||||
|
||||
private NestedMetaData(String field, int offset, @Nullable NestedMetaData child) {
|
||||
this.field = field;
|
||||
this.offset = offset;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NestedMetaData getChild() {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -69,5 +69,24 @@ public interface SearchDocument extends Document {
|
||||
*/
|
||||
@Nullable
|
||||
default Map<String, List<String>> getHighlightFields() {
|
||||
return null;}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innerHits for the SearchHit
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default Map<String, SearchDocumentResponse> getInnerHits() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the nested metadata in case this is a nested inner hit.
|
||||
* @since 4.1
|
||||
*/
|
||||
@Nullable
|
||||
default NestedMetaData getNestedMetaData() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-9
@@ -22,7 +22,9 @@ import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.lucene.search.TotalHits;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -42,7 +44,7 @@ public class SearchDocumentResponse {
|
||||
private final Aggregations aggregations;
|
||||
|
||||
private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId,
|
||||
List<SearchDocument> searchDocuments, Aggregations aggregations) {
|
||||
List<SearchDocument> searchDocuments, Aggregations aggregations) {
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
@@ -78,27 +80,45 @@ public class SearchDocumentResponse {
|
||||
/**
|
||||
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
||||
*
|
||||
* @param searchResponse
|
||||
* must not be {@literal null}
|
||||
* @param searchResponse must not be {@literal null}
|
||||
* @return the SearchDocumentResponse
|
||||
*/
|
||||
public static SearchDocumentResponse from(SearchResponse searchResponse) {
|
||||
|
||||
Assert.notNull(searchResponse, "searchResponse must not be null");
|
||||
|
||||
TotalHits responseTotalHits = searchResponse.getHits().getTotalHits();
|
||||
Aggregations aggregations = searchResponse.getAggregations();
|
||||
String scrollId = searchResponse.getScrollId();
|
||||
|
||||
SearchHits searchHits = searchResponse.getHits();
|
||||
|
||||
SearchDocumentResponse searchDocumentResponse = from(searchHits, scrollId, aggregations);
|
||||
return searchDocumentResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId and aggregations
|
||||
*
|
||||
* @param searchHits the {@link SearchHits} to process
|
||||
* @param scrollId scrollId
|
||||
* @param aggregations aggregations
|
||||
* @return the {@link SearchDocumentResponse}
|
||||
* @since 4.1
|
||||
*/
|
||||
public static SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
|
||||
@Nullable Aggregations aggregations) {
|
||||
TotalHits responseTotalHits = searchHits.getTotalHits();
|
||||
long totalHits = responseTotalHits.value;
|
||||
String totalHitsRelation = responseTotalHits.relation.name();
|
||||
|
||||
float maxScore = searchResponse.getHits().getMaxScore();
|
||||
String scrollId = searchResponse.getScrollId();
|
||||
float maxScore = searchHits.getMaxScore();
|
||||
|
||||
List<SearchDocument> searchDocuments = StreamSupport.stream(searchResponse.getHits().spliterator(), false) //
|
||||
List<SearchDocument> searchDocuments = StreamSupport.stream(searchHits.spliterator(), false) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(DocumentAdapters::from) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Aggregations aggregations = searchResponse.getAggregations();
|
||||
|
||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* Callback being invoked after a domain object is materialized from a {@link Document} when reading results.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
* @see org.springframework.data.mapping.callback.EntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is materialized from a {@link Document}. Can return either the
|
||||
* same or a modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object (the result of the conversion).
|
||||
* @param document must not be {@literal null}.
|
||||
* @param indexCoordinates must not be {@literal null}.
|
||||
* @return the domain object that is the result of reading it from the {@link Document}.
|
||||
*/
|
||||
T onAfterConvert(T entity, Document document, IndexCoordinates indexCoordinates);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
|
||||
/**
|
||||
* Entity callback triggered after save of an entity.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
* @see EntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of
|
||||
* the domain object.
|
||||
*
|
||||
* @param entity the domain object that was saved.
|
||||
* @param index must not be {@literal null}.
|
||||
* @return the domain object that was persisted.
|
||||
*/
|
||||
T onAfterSave(T entity, IndexCoordinates index);
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link EntityCallback} to populate auditing related fields on an entity about to be saved.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
public class AuditingEntityCallback implements BeforeConvertCallback<Object>, Ordered {
|
||||
|
||||
private final ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AuditingEntityCallback} using the given {@link IsNewAwareAuditingHandler} provided by the
|
||||
* given {@link ObjectFactory}.
|
||||
*
|
||||
* @param auditingHandlerFactory must not be {@literal null}.
|
||||
*/
|
||||
public AuditingEntityCallback(ObjectFactory<IsNewAwareAuditingHandler> auditingHandlerFactory) {
|
||||
|
||||
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
|
||||
|
||||
this.auditingHandlerFactory = auditingHandlerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onBeforeConvert(Object entity, IndexCoordinates index) {
|
||||
return auditingHandlerFactory.getObject().markAudited(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* Callback being invoked before a domain object is converted to be persisted.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Callback method that will be invoked before an entity is persisted. Can return the same or a different instance of
|
||||
* the domain entity class.
|
||||
*
|
||||
* @param entity the entity being converted
|
||||
* @param index must not be {@literal null}.
|
||||
* @return the entity to be converted
|
||||
*/
|
||||
T onBeforeConvert(T entity, IndexCoordinates index);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* Callback being invoked after a domain object is materialized from a {@link Document} when reading results.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveAfterConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is materialized from a {@link Document}. Can return either the
|
||||
* same or a modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object (the result of the conversion).
|
||||
* @param document must not be {@literal null}.
|
||||
* @param indexCoordinates must not be {@literal null}.
|
||||
* @return a {@link Publisher} emitting the domain object that is the result of reading it from the {@link Document}.
|
||||
*/
|
||||
Publisher<T> onAfterConvert(T entity, Document document, IndexCoordinates indexCoordinates);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
|
||||
/**
|
||||
* Entity callback triggered after save of an entity.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
* @see ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveAfterSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of
|
||||
* the domain object.
|
||||
*
|
||||
* @param entity the domain object that was saved.
|
||||
* @param index must not be {@literal null}.
|
||||
* @return a {@link Publisher} emitting the domain object to be returned to the caller.
|
||||
*/
|
||||
Publisher<T> onAfterSave(T entity, IndexCoordinates index);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user