mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-22 03:52:10 +00:00
parent
4e39fb30bc
commit
4eb8f08ad8
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,3 +21,6 @@ target
|
|||||||
*.iws
|
*.iws
|
||||||
.idea
|
.idea
|
||||||
/.env
|
/.env
|
||||||
|
|
||||||
|
|
||||||
|
/zap.env
|
||||||
|
170
.mvn/wrapper/MavenWrapperDownloader.java
vendored
170
.mvn/wrapper/MavenWrapperDownloader.java
vendored
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2007-present the original author or authors.
|
* Copyright 2007-present the original author or authors.
|
||||||
*
|
*
|
||||||
@ -20,98 +21,95 @@ import java.util.Properties;
|
|||||||
|
|
||||||
public class MavenWrapperDownloader {
|
public class MavenWrapperDownloader {
|
||||||
|
|
||||||
private static final String WRAPPER_VERSION = "0.5.6";
|
private static final String WRAPPER_VERSION = "0.5.6";
|
||||||
/**
|
/**
|
||||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||||
*/
|
*/
|
||||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the default
|
||||||
* use instead of the default one.
|
* one.
|
||||||
*/
|
*/
|
||||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties";
|
||||||
".mvn/wrapper/maven-wrapper.properties";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path where the maven-wrapper.jar will be saved to.
|
* Path where the maven-wrapper.jar will be saved to.
|
||||||
*/
|
*/
|
||||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar";
|
||||||
".mvn/wrapper/maven-wrapper.jar";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the property which should be used to override the default download url for the wrapper.
|
* Name of the property which should be used to override the default download url for the wrapper.
|
||||||
*/
|
*/
|
||||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
System.out.println("- Downloader started");
|
System.out.println("- Downloader started");
|
||||||
File baseDirectory = new File(args[0]);
|
File baseDirectory = new File(args[0]);
|
||||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
System.out.println("- Using transport directory: " + baseDirectory.getAbsolutePath());
|
||||||
|
|
||||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||||
// wrapperUrl parameter.
|
// wrapperUrl parameter.
|
||||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||||
String url = DEFAULT_DOWNLOAD_URL;
|
String url = DEFAULT_DOWNLOAD_URL;
|
||||||
if(mavenWrapperPropertyFile.exists()) {
|
if (mavenWrapperPropertyFile.exists()) {
|
||||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||||
try {
|
try {
|
||||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||||
Properties mavenWrapperProperties = new Properties();
|
Properties mavenWrapperProperties = new Properties();
|
||||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if(mavenWrapperPropertyFileInputStream != null) {
|
if (mavenWrapperPropertyFileInputStream != null) {
|
||||||
mavenWrapperPropertyFileInputStream.close();
|
mavenWrapperPropertyFileInputStream.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Ignore ...
|
// Ignore ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("- Downloading from: " + url);
|
System.out.println("- Downloading from: " + url);
|
||||||
|
|
||||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||||
if(!outputFile.getParentFile().exists()) {
|
if (!outputFile.getParentFile().exists()) {
|
||||||
if(!outputFile.getParentFile().mkdirs()) {
|
if (!outputFile.getParentFile().mkdirs()) {
|
||||||
System.out.println(
|
System.out.println("- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
}
|
||||||
}
|
}
|
||||||
}
|
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
try {
|
||||||
try {
|
downloadFileFromURL(url, outputFile);
|
||||||
downloadFileFromURL(url, outputFile);
|
System.out.println("Done");
|
||||||
System.out.println("Done");
|
System.exit(0);
|
||||||
System.exit(0);
|
} catch (Throwable e) {
|
||||||
} catch (Throwable e) {
|
System.out.println("- Error downloading");
|
||||||
System.out.println("- Error downloading");
|
e.printStackTrace();
|
||||||
e.printStackTrace();
|
System.exit(1);
|
||||||
System.exit(1);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||||
String username = System.getenv("MVNW_USERNAME");
|
String username = System.getenv("MVNW_USERNAME");
|
||||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||||
Authenticator.setDefault(new Authenticator() {
|
Authenticator.setDefault(new Authenticator() {
|
||||||
@Override
|
@Override
|
||||||
protected PasswordAuthentication getPasswordAuthentication() {
|
protected PasswordAuthentication getPasswordAuthentication() {
|
||||||
return new PasswordAuthentication(username, password);
|
return new PasswordAuthentication(username, password);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
URL website = new URL(urlString);
|
URL website = new URL(urlString);
|
||||||
ReadableByteChannel rbc;
|
ReadableByteChannel rbc;
|
||||||
rbc = Channels.newChannel(website.openStream());
|
rbc = Channels.newChannel(website.openStream());
|
||||||
FileOutputStream fos = new FileOutputStream(destination);
|
FileOutputStream fos = new FileOutputStream(destination);
|
||||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||||
fos.close();
|
fos.close();
|
||||||
rbc.close();
|
rbc.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,7 @@ You find the contribution guidelines for Spring Data projects https://github.com
|
|||||||
== Running the test locally
|
== Running the test locally
|
||||||
|
|
||||||
In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance.
|
In order to run the tests locally with `./mvnw test` you need to have docker running because Spring Data Elasticsearch uses https://www.testcontainers.org/[Testcontainers] to start a local running Elasticsearch instance.
|
||||||
|
|
||||||
|
== Class names of the test classes
|
||||||
|
|
||||||
|
Tset classes that do depend on the client have either `ERHLC` (when using the deprecated Elasticsearch `RestHighLevelClient`) or `ELC` (the new `ElasticsearchClient`) in their name.
|
||||||
|
2
mvnw
vendored
2
mvnw
vendored
@ -162,7 +162,7 @@ fi
|
|||||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||||
|
|
||||||
# traverses directory structure from process work directory to filesystem root
|
# traverses directory structure from process work directory to filesystem root
|
||||||
# first directory with .mvn subdirectory is considered project base directory
|
# first directory with .mvn subdirectory is considered project transport directory
|
||||||
find_maven_basedir() {
|
find_maven_basedir() {
|
||||||
|
|
||||||
if [ -z "$1" ]
|
if [ -z "$1" ]
|
||||||
|
28
pom.xml
28
pom.xml
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<elasticsearch>7.17.1</elasticsearch>
|
<elasticsearch>7.17.1</elasticsearch>
|
||||||
|
<elasticsearch-java>7.17.1</elasticsearch-java>
|
||||||
<log4j>2.17.1</log4j>
|
<log4j>2.17.1</log4j>
|
||||||
<netty>4.1.65.Final</netty>
|
<netty>4.1.65.Final</netty>
|
||||||
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
|
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
|
||||||
@ -134,7 +135,7 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Elasticsearch -->
|
<!-- Elasticsearch RestHighLevelClient, will be removed probably in SDE 5 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.elasticsearch.client</groupId>
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||||
@ -147,6 +148,30 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- new Elasticsearch client, needs the low-level rest client and json api -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>${elasticsearch-java}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
|
||||||
|
<version>${elasticsearch}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Jackson JSON Mapper -->
|
<!-- Jackson JSON Mapper -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
@ -480,6 +505,7 @@
|
|||||||
<id>local-maven-repo</id>
|
<id>local-maven-repo</id>
|
||||||
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
|
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
|
@ -6,14 +6,159 @@ This section describes breaking changes from version 4.3.x to 4.4.x and how remo
|
|||||||
[[elasticsearch-migration-guide-4.3-4.4.deprecations]]
|
[[elasticsearch-migration-guide-4.3-4.4.deprecations]]
|
||||||
== Deprecations
|
== Deprecations
|
||||||
|
|
||||||
|
=== org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations
|
||||||
|
|
||||||
|
The method `<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback)` has been deprecated.
|
||||||
|
As there now are multiple implementations using different client libraries the `execute` method is still available in the different implementations, but there is no more method in the interface, because there is no common callback interface for the different clients.
|
||||||
|
|
||||||
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes]]
|
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes]]
|
||||||
== Breaking Changes
|
== Breaking Changes
|
||||||
|
|
||||||
=== Removal of deprecated classes
|
=== Removal of deprecated classes
|
||||||
|
|
||||||
==== `ElasticsearchTemplate` has been removed
|
==== `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` has been removed
|
||||||
|
|
||||||
As of version 4.4 Spring Data Elasticsearch does not use the `TransportClient` from Elasticsearch anymore (which itself is deprecated since Elasticsearch 7.0).
|
As of version 4.4 Spring Data Elasticsearch does not use the `TransportClient` from Elasticsearch anymore (which itself is deprecated since Elasticsearch 7.0).
|
||||||
This means that the `ElasticsearchTemplate` class which was deprecated since Spring Data Elasticsearch 4.0 has been removed.
|
This means that the `org.springframework.data.elasticsearch.core.ElasticsearchTemplate` class which was deprecated since Spring Data Elasticsearch 4.0 has been removed.
|
||||||
This was the implementation of the `ElasticsearchOperations` interface that was using the `TransportClient`.
|
This was the implementation of the `ElasticsearchOperations` interface that was using the `TransportClient`.
|
||||||
Connections to Elasticsearch must be made using either the imperative `ElasticsearchRestTemplate` or the reactive `ReactiveElasticsearchTemplate`.
|
Connections to Elasticsearch must be made using either the imperative `ElasticsearchRestTemplate` or the reactive `ReactiveElasticsearchTemplate`.
|
||||||
|
|
||||||
|
=== Package changes
|
||||||
|
|
||||||
|
In 4.3 two classes (`ElasticsearchAggregations` and `ElasticsearchAggregation`) had been moved to the `org.springframework.data.elasticsearch.core.clients.elasticsearch7` package in preparation for the integration of the new Elasticsearch client.
|
||||||
|
The were moved back to the `org.springframework.data.elasticsearch.core` package as we keep the classes use the old Elasticsearch client where they were.
|
||||||
|
|
||||||
|
[[elasticsearch-migration-guide-4.3-4.4.new-clients]]
|
||||||
|
== New Elasticsearch client
|
||||||
|
|
||||||
|
Elasticsearch has introduced it's new `ElasticsearchClient` and has deprecated the previous `RestHighLevelClient`.
|
||||||
|
Spring Data Elasticsearch 4.4 still uses the old client as the default client for the following reasons:
|
||||||
|
|
||||||
|
* The new client forces applications to use the `jakarta.json.spi.JsonProvider` package whereas Spring Boot will stick to `javax.json.spi.JsonProvider` until version 3. So switching the default implementaiton in Spring Data Elasticsearch can only come with Spring Data Elasticsearch 5 (Spring Data 3, Spring 6).
|
||||||
|
* There are still some bugs in the Elasticsearch client which need to be resolved
|
||||||
|
* The implementation using the new client in Spring Data Elasticsearch is not yet complete, due to limited resources working on that - remember Spring Data Elasticsearch is a community driven project that lives from public contributions.
|
||||||
|
|
||||||
|
=== How to use the new client
|
||||||
|
|
||||||
|
CAUTION: The implementation using the new client is not complete, some operations will throw a `java.lang.UnsupportedOperationException` or might throw NPE (for example when the Elasticsearch cannot parse a response from the server, this still happens sometimes) +
|
||||||
|
Use the new client to test the implementations but do not use it in productive code yet!
|
||||||
|
|
||||||
|
In order to try and use the new client the following steps are necessary:
|
||||||
|
|
||||||
|
==== Make sure not to configure the existing default client
|
||||||
|
|
||||||
|
If using Spring Boot, exclude Spring Data Elasticsearch from the autoconfiguration
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@SpringBootApplication(exclude = ElasticsearchDataAutoConfiguration.class)
|
||||||
|
public class SpringdataElasticTestApplication {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Remove Spring Data Elasticsearch related properties from your application configuration.
|
||||||
|
If Spring Data Elasticsearch was configured using a programmatic configuration (see <<elastisearch.clients>>), remove these beans from the Spring application context.
|
||||||
|
|
||||||
|
==== Add dependencies
|
||||||
|
|
||||||
|
The dependencies for the new Elasticsearch client are still optional in Spring Data Elasticsearch so they need to be added explicitly:
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,xml]
|
||||||
|
----
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>7.17.1</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
|
||||||
|
<version>7.17.1</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
When using Spring Boot, it is necessary to set the following property in the _pom.xml_.
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,xml]
|
||||||
|
----
|
||||||
|
<properties>
|
||||||
|
<jakarta-json.version>2.0.1</jakarta-json.version>
|
||||||
|
</properties>
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
==== New configuration classes
|
||||||
|
|
||||||
|
===== Imperative style
|
||||||
|
|
||||||
|
In order configure Spring Data Elasticsearch to use the new client, it is necessary to create a configuration bean that derives from `org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration`:
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class NewRestClientConfig extends ElasticsearchConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConfiguration clientConfiguration() {
|
||||||
|
return ClientConfiguration.builder() //
|
||||||
|
.connectedTo("localhost:9200") //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The configuration is done in the same way as with the old client, but it is not necessary anymore to create more than the configuration bean.
|
||||||
|
With this configuration, the following beans will be available in the Spring application context:
|
||||||
|
|
||||||
|
* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client
|
||||||
|
* an `ElasticsearchClient` bean, this is the new client that uses the `RestClient`
|
||||||
|
* an `ElasticsearchOperations` bean, available with the bean names _elasticsearchOperations_ and _elasticsearchTemplate_, this uses the `ElasticsearchClient`
|
||||||
|
|
||||||
|
===== Reactive style
|
||||||
|
|
||||||
|
To use the new client in a reactive environment the only difference is the class from which to derive the configuration:
|
||||||
|
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Configuration
|
||||||
|
public class NewRestClientConfig extends ReactiveElasticsearchConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConfiguration clientConfiguration() {
|
||||||
|
return ClientConfiguration.builder() //
|
||||||
|
.connectedTo("localhost:9200") //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
With this configuration, the following beans will be available in the Spring application context:
|
||||||
|
|
||||||
|
* a `RestClient` bean, that is the configured low level `RestClient` that is used by the Elasticsearch client
|
||||||
|
* an `ReactiveElasticsearchClient` bean, this is the new reactive client that uses the `RestClient`
|
||||||
|
* an `ReactiveElasticsearchOperations` bean, available with the bean names _reactiveElasticsearchOperations_ and _reactiveElasticsearchTemplate_, this uses the `ReactiveElasticsearchClient`
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
[[new-features.4-4-0]]
|
[[new-features.4-4-0]]
|
||||||
== New in Spring Data Elasticsearch 4.4
|
== New in Spring Data Elasticsearch 4.4
|
||||||
|
|
||||||
|
* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client
|
||||||
* Upgrade to Elasticsearch 7.17.1.
|
* Upgrade to Elasticsearch 7.17.1.
|
||||||
|
|
||||||
[[new-features.4-3-0]]
|
[[new-features.4-3-0]]
|
||||||
|
@ -10,4 +10,6 @@ include::elasticsearch-migration-guide-4.0-4.1.adoc[]
|
|||||||
include::elasticsearch-migration-guide-4.1-4.2.adoc[]
|
include::elasticsearch-migration-guide-4.1-4.2.adoc[]
|
||||||
|
|
||||||
include::elasticsearch-migration-guide-4.2-4.3.adoc[]
|
include::elasticsearch-migration-guide-4.2-4.3.adoc[]
|
||||||
|
|
||||||
|
include::elasticsearch-migration-guide-4.3-4.4.adoc[]
|
||||||
:leveloffset: -1
|
:leveloffset: -1
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object describing an Elasticsearch error
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ElasticsearchErrorCause {
|
||||||
|
@Nullable private final String type;
|
||||||
|
|
||||||
|
private final String reason;
|
||||||
|
|
||||||
|
@Nullable private final String stackTrace;
|
||||||
|
|
||||||
|
@Nullable private final ElasticsearchErrorCause causedBy;
|
||||||
|
|
||||||
|
private final List<ElasticsearchErrorCause> rootCause;
|
||||||
|
|
||||||
|
private final List<ElasticsearchErrorCause> suppressed;
|
||||||
|
|
||||||
|
public ElasticsearchErrorCause(@Nullable String type, String reason, @Nullable String stackTrace,
|
||||||
|
@Nullable ElasticsearchErrorCause causedBy, List<ElasticsearchErrorCause> rootCause,
|
||||||
|
List<ElasticsearchErrorCause> suppressed) {
|
||||||
|
this.type = type;
|
||||||
|
this.reason = reason;
|
||||||
|
this.stackTrace = stackTrace;
|
||||||
|
this.causedBy = causedBy;
|
||||||
|
this.rootCause = rootCause;
|
||||||
|
this.suppressed = suppressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getStackTrace() {
|
||||||
|
return stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ElasticsearchErrorCause getCausedBy() {
|
||||||
|
return causedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ElasticsearchErrorCause> getRootCause() {
|
||||||
|
return rootCause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ElasticsearchErrorCause> getSuppressed() {
|
||||||
|
return suppressed;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
package org.springframework.data.elasticsearch;
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
import org.springframework.dao.UncategorizedDataAccessException;
|
import org.springframework.dao.UncategorizedDataAccessException;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
@ -23,11 +24,48 @@ import org.springframework.dao.UncategorizedDataAccessException;
|
|||||||
*/
|
*/
|
||||||
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
|
public class UncategorizedElasticsearchException extends UncategorizedDataAccessException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the response status code from Elasticsearch if available
|
||||||
|
*
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@Nullable private final Integer statusCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response body from Elasticsearch if available
|
||||||
|
*
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@Nullable final String responseBody;
|
||||||
|
|
||||||
public UncategorizedElasticsearchException(String msg) {
|
public UncategorizedElasticsearchException(String msg) {
|
||||||
super(msg, null);
|
this(msg, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UncategorizedElasticsearchException(String msg, Throwable cause) {
|
public UncategorizedElasticsearchException(String msg, @Nullable Throwable cause) {
|
||||||
|
this(msg, null, null, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UncategorizedElasticsearchException(String msg, @Nullable Integer statusCode, @Nullable String responseBody,
|
||||||
|
@Nullable Throwable cause) {
|
||||||
super(msg, cause);
|
super(msg, cause);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.responseBody = responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Integer getStatusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getResponseBody() {
|
||||||
|
return responseBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,22 @@ public @interface Document {
|
|||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
enum VersionType {
|
enum VersionType {
|
||||||
INTERNAL, EXTERNAL, EXTERNAL_GTE
|
INTERNAL("internal"), //
|
||||||
|
EXTERNAL("external"), //
|
||||||
|
EXTERNAL_GTE("external_gte"), //
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
FORCE("force");
|
||||||
|
|
||||||
|
private final String esName;
|
||||||
|
|
||||||
|
VersionType(String esName) {
|
||||||
|
this.esName = esName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEsName() {
|
||||||
|
return esName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,11 @@ import org.springframework.util.ObjectUtils;
|
|||||||
*
|
*
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public abstract class ClientLogger {
|
public abstract class ClientLogger {
|
||||||
|
|
||||||
private static final String lineSeparator = System.getProperty("line.separator");
|
|
||||||
private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE");
|
private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE");
|
||||||
|
|
||||||
private ClientLogger() {}
|
private ClientLogger() {}
|
||||||
@ -63,6 +63,24 @@ public abstract class ClientLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an outgoing HTTP request.
|
||||||
|
*
|
||||||
|
* @param logId the correlation id, see {@link #newLogId()}.
|
||||||
|
* @param method HTTP method
|
||||||
|
* @param endpoint URI
|
||||||
|
* @param parameters optional parameters.
|
||||||
|
* @param headers a String containing the headers
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers) {
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s", logId,
|
||||||
|
method.toUpperCase(), endpoint, parameters, headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log an outgoing HTTP request with a request body.
|
* Log an outgoing HTTP request with a request body.
|
||||||
*
|
*
|
||||||
@ -76,8 +94,28 @@ public abstract class ClientLogger {
|
|||||||
Supplier<Object> body) {
|
Supplier<Object> body) {
|
||||||
|
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%sRequest body: %s", logId,
|
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%nRequest body: %s", logId,
|
||||||
method.toUpperCase(), endpoint, parameters, lineSeparator, body.get()));
|
method.toUpperCase(), endpoint, parameters, body.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an outgoing HTTP request with a request body.
|
||||||
|
*
|
||||||
|
* @param logId the correlation id, see {@link #newLogId()}.
|
||||||
|
* @param method HTTP method
|
||||||
|
* @param endpoint URI
|
||||||
|
* @param parameters optional parameters.
|
||||||
|
* @param headers a String containing the headers
|
||||||
|
* @param body body content supplier.
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public static void logRequest(String logId, String method, String endpoint, Object parameters, String headers,
|
||||||
|
Supplier<Object> body) {
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
WIRE_LOGGER.trace(String.format("[%s] Sending request%n%s %s%nParameters: %s%nHeaders: %s%nRequest body: %s",
|
||||||
|
logId, method.toUpperCase(), endpoint, parameters, headers, body.get()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +132,20 @@ public abstract class ClientLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a raw HTTP response without logging the body.
|
||||||
|
*
|
||||||
|
* @param logId the correlation id, see {@link #newLogId()}.
|
||||||
|
* @param statusCode the HTTP status code.
|
||||||
|
* @param headers a String containing the headers
|
||||||
|
*/
|
||||||
|
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode, String headers) {
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%n%s", logId, statusCode, headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a raw HTTP response along with the body.
|
* Log a raw HTTP response along with the body.
|
||||||
*
|
*
|
||||||
@ -104,8 +156,24 @@ public abstract class ClientLogger {
|
|||||||
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
||||||
|
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
WIRE_LOGGER.trace(
|
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, body));
|
||||||
String.format("[%s] Received response: %s%sResponse body: %s", logId, statusCode, lineSeparator, body));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a raw HTTP response along with the body.
|
||||||
|
*
|
||||||
|
* @param logId the correlation id, see {@link #newLogId()}.
|
||||||
|
* @param statusCode the HTTP status code.
|
||||||
|
* @param headers a String containing the headers
|
||||||
|
* @param body body content.
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public static void logResponse(String logId, @Nullable HttpStatus statusCode, String headers, String body) {
|
||||||
|
|
||||||
|
if (isEnabled()) {
|
||||||
|
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nHeaders: %s%nResponse body: %s", logId, statusCode,
|
||||||
|
headers, body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception to be thrown by a backend implementation on operations that are not supported for that backend.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class UnsupportedBackendOperation extends RuntimeException {
|
||||||
|
public UnsupportedBackendOperation() {}
|
||||||
|
|
||||||
|
public UnsupportedBackendOperation(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedBackendOperation(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedBackendOperation(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedBackendOperation(String message, Throwable cause, boolean enableSuppression,
|
||||||
|
boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of the {@link ElasticsearchClient} class that implements {@link AutoCloseable}. As the underlying
|
||||||
|
* {@link RestClient} must be closed properly this is handled in the {@link #close()} method.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class AutoCloseableElasticsearchClient extends ElasticsearchClient implements AutoCloseable {
|
||||||
|
|
||||||
|
public AutoCloseableElasticsearchClient(ElasticsearchTransport transport) {
|
||||||
|
super(transport);
|
||||||
|
Assert.notNull(transport, "transport must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElasticsearchClusterClient cluster() {
|
||||||
|
return super.cluster();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.ApiClient;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base class for a template that uses one of the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}'s child
|
||||||
|
* clients like {@link ElasticsearchClusterClient} or
|
||||||
|
* {@link co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public abstract class ChildTemplate<CLIENT extends ApiClient> {
|
||||||
|
|
||||||
|
protected final CLIENT client;
|
||||||
|
protected final RequestConverter requestConverter;
|
||||||
|
protected final ResponseConverter responseConverter;
|
||||||
|
protected final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||||
|
|
||||||
|
public ChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
this.client = client;
|
||||||
|
JsonpMapper jsonpMapper = client._transport().jsonpMapper();
|
||||||
|
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
|
||||||
|
responseConverter = new ResponseConverter(jsonpMapper);
|
||||||
|
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on the client.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ClientCallback<CLIENT, RESULT> {
|
||||||
|
RESULT doWithClient(CLIENT client) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a callback with the client and provide exception translation.
|
||||||
|
*
|
||||||
|
* @param callback the callback to execute, must not be {@literal null}
|
||||||
|
* @param <RESULT> the type returned from the callback
|
||||||
|
* @return the callback result
|
||||||
|
*/
|
||||||
|
public <RESULT> RESULT execute(ClientCallback<CLIENT, RESULT> callback) {
|
||||||
|
|
||||||
|
Assert.notNull(callback, "callback must not be null");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return callback.doWithClient(client);
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
throw exceptionTranslator.translateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the {@link ClusterOperations} interface using en {@link ElasticsearchClusterClient}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ClusterTemplate extends ChildTemplate<ElasticsearchClusterClient> implements ClusterOperations {
|
||||||
|
|
||||||
|
public ClusterTemplate(ElasticsearchClusterClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClusterHealth health() {
|
||||||
|
|
||||||
|
HealthRequest healthRequest = requestConverter.clusterHealthRequest();
|
||||||
|
HealthResponse healthResponse = execute(client -> client.health(healthRequest));
|
||||||
|
return responseConverter.clusterHealth(healthResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.GeoDistanceType;
|
||||||
|
import co.elastic.clients.elasticsearch._types.GeoShapeRelation;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.GeoBoundingBoxQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.GeoDistanceQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.GeoShapeQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.QueryVariant;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.GeoConverters;
|
||||||
|
import org.springframework.data.elasticsearch.core.geo.GeoBox;
|
||||||
|
import org.springframework.data.elasticsearch.core.geo.GeoJson;
|
||||||
|
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||||
|
import org.springframework.data.geo.Box;
|
||||||
|
import org.springframework.data.geo.Distance;
|
||||||
|
import org.springframework.data.geo.Metrics;
|
||||||
|
import org.springframework.data.geo.Point;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to convert a {@link org.springframework.data.elasticsearch.core.query.CriteriaQuery} into an Elasticsearch
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
class CriteriaFilterProcessor {
|
||||||
|
/**
|
||||||
|
* Creates a filter query from the given criteria.
|
||||||
|
*
|
||||||
|
* @param criteria the criteria to process
|
||||||
|
* @return the optional query, empty if the criteria did not contain filter relevant elements
|
||||||
|
*/
|
||||||
|
public static Optional<Query> createQuery(Criteria criteria) {
|
||||||
|
|
||||||
|
Assert.notNull(criteria, "criteria must not be null");
|
||||||
|
|
||||||
|
List<Query> filterQueries = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
|
||||||
|
|
||||||
|
if (chainedCriteria.isOr()) {
|
||||||
|
// todo #1973
|
||||||
|
} else if (chainedCriteria.isNegating()) {
|
||||||
|
// todo #1973
|
||||||
|
} else {
|
||||||
|
filterQueries.addAll(queriesForEntries(chainedCriteria));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterQueries.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (filterQueries.size() == 1) {
|
||||||
|
return Optional.of(filterQueries.get(0));
|
||||||
|
} else {
|
||||||
|
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
|
||||||
|
filterQueries.forEach(boolQueryBuilder::must);
|
||||||
|
BoolQuery boolQuery = boolQueryBuilder.build();
|
||||||
|
return Optional.of(boolQuery._toQuery());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<? extends Query> queriesForEntries(Criteria criteria) {
|
||||||
|
|
||||||
|
Assert.notNull(criteria.getField(), "criteria must have a field");
|
||||||
|
String fieldName = criteria.getField().getName();
|
||||||
|
Assert.notNull(fieldName, "Unknown field");
|
||||||
|
|
||||||
|
return criteria.getFilterCriteriaEntries().stream()
|
||||||
|
.map(entry -> queryFor(entry.getKey(), entry.getValue(), fieldName)) //
|
||||||
|
.filter(Optional::isPresent) //
|
||||||
|
.map(Optional::get) //
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Optional<Query> queryFor(Criteria.OperationKey key, Object value, String fieldName) {
|
||||||
|
|
||||||
|
ObjectBuilder<? extends QueryVariant> queryBuilder = null;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case WITHIN:
|
||||||
|
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
|
||||||
|
queryBuilder = withinQuery(fieldName, (Object[]) value);
|
||||||
|
break;
|
||||||
|
case BBOX:
|
||||||
|
Assert.isTrue(value instanceof Object[],
|
||||||
|
"Value of a boundedBy filter should be an array of one or two values.");
|
||||||
|
queryBuilder = boundingBoxQuery(fieldName, (Object[]) value);
|
||||||
|
break;
|
||||||
|
case GEO_INTERSECTS:
|
||||||
|
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
|
||||||
|
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
|
||||||
|
break;
|
||||||
|
case GEO_IS_DISJOINT:
|
||||||
|
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
|
||||||
|
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
|
||||||
|
break;
|
||||||
|
case GEO_WITHIN:
|
||||||
|
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
|
||||||
|
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
|
||||||
|
break;
|
||||||
|
case GEO_CONTAINS:
|
||||||
|
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
|
||||||
|
queryBuilder = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(queryBuilder != null ? queryBuilder.build()._toQuery() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectBuilder<GeoDistanceQuery> withinQuery(String fieldName, Object[] values) {
|
||||||
|
|
||||||
|
Assert.noNullElements(values, "Geo distance filter takes 2 not null elements array as parameter.");
|
||||||
|
Assert.isTrue(values.length == 2, "Geo distance filter takes a 2-elements array as parameter.");
|
||||||
|
Assert.isTrue(values[0] instanceof GeoPoint || values[0] instanceof String || values[0] instanceof Point,
|
||||||
|
"First element of a geo distance filter must be a GeoPoint, a Point or a text");
|
||||||
|
Assert.isTrue(values[1] instanceof String || values[1] instanceof Distance,
|
||||||
|
"Second element of a geo distance filter must be a text or a Distance");
|
||||||
|
|
||||||
|
String dist = (values[1] instanceof Distance) ? extractDistanceString((Distance) values[1]) : (String) values[1];
|
||||||
|
|
||||||
|
return QueryBuilders.geoDistance() //
|
||||||
|
.field(fieldName) //
|
||||||
|
.distance(dist) //
|
||||||
|
.distanceType(GeoDistanceType.Plane) //
|
||||||
|
.location(location -> {
|
||||||
|
if (values[0] instanceof GeoPoint) {
|
||||||
|
GeoPoint loc = (GeoPoint) values[0];
|
||||||
|
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
|
||||||
|
} else if (values[0] instanceof Point) {
|
||||||
|
GeoPoint loc = GeoPoint.fromPoint((Point) values[0]);
|
||||||
|
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
|
||||||
|
} else {
|
||||||
|
String loc = (String) values[0];
|
||||||
|
if (loc.contains(",")) {
|
||||||
|
String[] c = loc.split(",");
|
||||||
|
location.latlon(latlon -> latlon.lat(Double.parseDouble(c[0])).lon(Double.parseDouble(c[1])));
|
||||||
|
} else {
|
||||||
|
location.geohash(geohash -> geohash.geohash(loc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return location;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectBuilder<GeoBoundingBoxQuery> boundingBoxQuery(String fieldName, Object[] values) {
|
||||||
|
|
||||||
|
Assert.noNullElements(values, "Geo boundedBy filter takes a not null element array as parameter.");
|
||||||
|
|
||||||
|
GeoBoundingBoxQuery.Builder queryBuilder = QueryBuilders.geoBoundingBox() //
|
||||||
|
.field(fieldName);
|
||||||
|
|
||||||
|
if (values.length == 1) {
|
||||||
|
// GeoEnvelop
|
||||||
|
oneParameterBBox(queryBuilder, values[0]);
|
||||||
|
} else if (values.length == 2) {
|
||||||
|
// 2x GeoPoint
|
||||||
|
// 2x text
|
||||||
|
twoParameterBBox(queryBuilder, values);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
|
||||||
|
}
|
||||||
|
return queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void oneParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object value) {
|
||||||
|
Assert.isTrue(value instanceof GeoBox || value instanceof Box,
|
||||||
|
"single-element of boundedBy filter must be type of GeoBox or Box");
|
||||||
|
|
||||||
|
GeoBox geoBBox;
|
||||||
|
if (value instanceof Box) {
|
||||||
|
geoBBox = GeoBox.fromBox((Box) value);
|
||||||
|
} else {
|
||||||
|
geoBBox = (GeoBox) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBuilder.boundingBox(bb -> bb //
|
||||||
|
.tlbr(tlbr -> tlbr //
|
||||||
|
.topLeft(glb -> glb //
|
||||||
|
.latlon(latlon -> latlon //
|
||||||
|
.lat(geoBBox.getTopLeft().getLat()) //
|
||||||
|
.lon(geoBBox.getTopLeft().getLon()))) //
|
||||||
|
.bottomRight(glb -> glb //
|
||||||
|
.latlon(latlon -> latlon //
|
||||||
|
.lat(geoBBox.getBottomRight().getLat())//
|
||||||
|
.lon(geoBBox.getBottomRight().getLon()// )
|
||||||
|
)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void twoParameterBBox(GeoBoundingBoxQuery.Builder queryBuilder, Object[] values) {
|
||||||
|
|
||||||
|
Assert.isTrue(allElementsAreOfType(values, GeoPoint.class) || allElementsAreOfType(values, String.class),
|
||||||
|
" both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
|
||||||
|
|
||||||
|
if (values[0] instanceof GeoPoint) {
|
||||||
|
GeoPoint topLeft = (GeoPoint) values[0];
|
||||||
|
GeoPoint bottomRight = (GeoPoint) values[1];
|
||||||
|
queryBuilder.boundingBox(bb -> bb //
|
||||||
|
.tlbr(tlbr -> tlbr //
|
||||||
|
.topLeft(glb -> glb //
|
||||||
|
.latlon(latlon -> latlon //
|
||||||
|
.lat(topLeft.getLat()) //
|
||||||
|
.lon(topLeft.getLon()))) //
|
||||||
|
.bottomRight(glb -> glb //
|
||||||
|
.latlon(latlon -> latlon //
|
||||||
|
.lat(bottomRight.getLat()) //
|
||||||
|
.lon(bottomRight.getLon()))) //
|
||||||
|
) //
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
String topLeft = (String) values[0];
|
||||||
|
String bottomRight = (String) values[1];
|
||||||
|
boolean isGeoHash = !topLeft.contains(",");
|
||||||
|
queryBuilder.boundingBox(bb -> bb //
|
||||||
|
.tlbr(tlbr -> tlbr //
|
||||||
|
.topLeft(glb -> {
|
||||||
|
if (isGeoHash) {
|
||||||
|
glb.geohash(gh -> gh.geohash(topLeft));
|
||||||
|
} else {
|
||||||
|
glb.text(topLeft);
|
||||||
|
}
|
||||||
|
return glb;
|
||||||
|
}) //
|
||||||
|
.bottomRight(glb -> {
|
||||||
|
if (isGeoHash) {
|
||||||
|
glb.geohash(gh -> gh.geohash(bottomRight));
|
||||||
|
} else {
|
||||||
|
glb.text(bottomRight);
|
||||||
|
}
|
||||||
|
return glb;
|
||||||
|
}) //
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean allElementsAreOfType(Object[] array, Class<?> clazz) {
|
||||||
|
for (Object o : array) {
|
||||||
|
if (!clazz.isInstance(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectBuilder<? extends QueryVariant> geoJsonQuery(String fieldName, GeoJson<?> geoJson,
|
||||||
|
String relation) {
|
||||||
|
return buildGeoShapeQuery(fieldName, geoJson, relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectBuilder<GeoShapeQuery> buildGeoShapeQuery(String fieldName, GeoJson<?> geoJson,
|
||||||
|
String relation) {
|
||||||
|
return QueryBuilders.geoShape().field(fieldName) //
|
||||||
|
.shape(gsf -> gsf //
|
||||||
|
.shape(JsonData.of(GeoConverters.GeoJsonToMapConverter.INSTANCE.convert(geoJson))) //
|
||||||
|
.relation(toRelation(relation))); //
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GeoShapeRelation toRelation(String relation) {
|
||||||
|
|
||||||
|
for (GeoShapeRelation geoShapeRelation : GeoShapeRelation.values()) {
|
||||||
|
|
||||||
|
if (geoShapeRelation.name().equalsIgnoreCase(relation)) {
|
||||||
|
return geoShapeRelation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown geo_shape relation: " + relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extract the distance string from a {@link org.springframework.data.geo.Distance} object.
|
||||||
|
*
|
||||||
|
* @param distance distance object to extract string from
|
||||||
|
*/
|
||||||
|
private static String extractDistanceString(Distance distance) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append((int) distance.getValue());
|
||||||
|
switch ((Metrics) distance.getMetric()) {
|
||||||
|
case KILOMETERS:
|
||||||
|
sb.append("km");
|
||||||
|
break;
|
||||||
|
case MILES:
|
||||||
|
sb.append("mi");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright2020-2021 the original author or authors.
|
* Copyright 2021-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -13,13 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.routing;
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
import org.springframework.dao.UncategorizedDataAccessException;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
public class CriteriaQueryException extends UncategorizedDataAccessException {
|
||||||
public class RoutingRestTemplateIntegrationTests extends RoutingIntegrationTests {}
|
public CriteriaQueryException(String msg) {
|
||||||
|
super(msg, null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,368 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.springframework.data.elasticsearch.client.elc.QueryBuilders.*;
|
||||||
|
import static org.springframework.util.StringUtils.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Field;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to convert a {@link org.springframework.data.elasticsearch.core.query.CriteriaQuery} into an Elasticsearch
|
||||||
|
* query.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
class CriteriaQueryProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a query from the criteria
|
||||||
|
*
|
||||||
|
* @param criteria the {@link Criteria}
|
||||||
|
* @return the optional query, null if the criteria did not contain filter relevant elements
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Query createQuery(Criteria criteria) {
|
||||||
|
|
||||||
|
Assert.notNull(criteria, "criteria must not be null");
|
||||||
|
|
||||||
|
List<Query> shouldQueries = new ArrayList<>();
|
||||||
|
List<Query> mustNotQueries = new ArrayList<>();
|
||||||
|
List<Query> mustQueries = new ArrayList<>();
|
||||||
|
|
||||||
|
Query firstQuery = null;
|
||||||
|
boolean negateFirstQuery = false;
|
||||||
|
|
||||||
|
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
|
||||||
|
Query queryFragment = queryForEntries(chainedCriteria);
|
||||||
|
|
||||||
|
if (queryFragment != null) {
|
||||||
|
|
||||||
|
if (firstQuery == null) {
|
||||||
|
firstQuery = queryFragment;
|
||||||
|
negateFirstQuery = chainedCriteria.isNegating();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainedCriteria.isOr()) {
|
||||||
|
shouldQueries.add(queryFragment);
|
||||||
|
} else if (chainedCriteria.isNegating()) {
|
||||||
|
mustNotQueries.add(queryFragment);
|
||||||
|
} else {
|
||||||
|
mustQueries.add(queryFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Criteria subCriteria : criteria.getSubCriteria()) {
|
||||||
|
Query subQuery = createQuery(subCriteria);
|
||||||
|
if (subQuery != null) {
|
||||||
|
if (criteria.isOr()) {
|
||||||
|
shouldQueries.add(subQuery);
|
||||||
|
} else if (criteria.isNegating()) {
|
||||||
|
mustNotQueries.add(subQuery);
|
||||||
|
} else {
|
||||||
|
mustQueries.add(subQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstQuery != null) {
|
||||||
|
|
||||||
|
if (!shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
|
||||||
|
shouldQueries.add(0, firstQuery);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (negateFirstQuery) {
|
||||||
|
mustNotQueries.add(0, firstQuery);
|
||||||
|
} else {
|
||||||
|
mustQueries.add(0, firstQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Query query = new Query.Builder().bool(boolQueryBuilder -> {
|
||||||
|
|
||||||
|
if (!shouldQueries.isEmpty()) {
|
||||||
|
boolQueryBuilder.should(shouldQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mustNotQueries.isEmpty()) {
|
||||||
|
boolQueryBuilder.mustNot(mustNotQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mustQueries.isEmpty()) {
|
||||||
|
boolQueryBuilder.must(mustQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolQueryBuilder;
|
||||||
|
}).build();
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Query queryForEntries(Criteria criteria) {
|
||||||
|
|
||||||
|
Field field = criteria.getField();
|
||||||
|
|
||||||
|
if (field == null || criteria.getQueryCriteriaEntries().isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String fieldName = field.getName();
|
||||||
|
Assert.notNull(fieldName, "Unknown field " + fieldName);
|
||||||
|
|
||||||
|
Iterator<Criteria.CriteriaEntry> it = criteria.getQueryCriteriaEntries().iterator();
|
||||||
|
|
||||||
|
Float boost = Float.isNaN(criteria.getBoost()) ? null : criteria.getBoost();
|
||||||
|
Query.Builder queryBuilder;
|
||||||
|
|
||||||
|
if (criteria.getQueryCriteriaEntries().size() == 1) {
|
||||||
|
queryBuilder = queryFor(it.next(), field, boost);
|
||||||
|
} else {
|
||||||
|
queryBuilder = new Query.Builder();
|
||||||
|
queryBuilder.bool(boolQueryBuilder -> {
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Criteria.CriteriaEntry entry = it.next();
|
||||||
|
boolQueryBuilder.must(queryFor(entry, field, null).build());
|
||||||
|
}
|
||||||
|
boolQueryBuilder.boost(boost);
|
||||||
|
return boolQueryBuilder;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasText(field.getPath())) {
|
||||||
|
final Query query = queryBuilder.build();
|
||||||
|
queryBuilder = new Query.Builder();
|
||||||
|
queryBuilder.nested(nqb -> nqb //
|
||||||
|
.path(field.getPath()) //
|
||||||
|
.query(query) //
|
||||||
|
.scoreMode(ChildScoreMode.Avg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field, @Nullable Float boost) {
|
||||||
|
|
||||||
|
String fieldName = field.getName();
|
||||||
|
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
|
||||||
|
|
||||||
|
Criteria.OperationKey key = entry.getKey();
|
||||||
|
Object value = key.hasValue() ? entry.getValue() : null;
|
||||||
|
String searchText = value != null ? QueryParserUtil.escape(value.toString()) : "UNKNOWN_VALUE";
|
||||||
|
|
||||||
|
Query.Builder queryBuilder = new Query.Builder();
|
||||||
|
switch (key) {
|
||||||
|
case EXISTS:
|
||||||
|
queryBuilder //
|
||||||
|
.exists(eb -> eb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.boost(boost));
|
||||||
|
break;
|
||||||
|
case EMPTY:
|
||||||
|
queryBuilder //
|
||||||
|
.bool(bb -> bb //
|
||||||
|
.must(mb -> mb //
|
||||||
|
.exists(eb -> eb //
|
||||||
|
.field(fieldName) //
|
||||||
|
)) //
|
||||||
|
.mustNot(mnb -> mnb //
|
||||||
|
.wildcard(wb -> wb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.wildcard("*"))) //
|
||||||
|
.boost(boost));
|
||||||
|
break;
|
||||||
|
case NOT_EMPTY:
|
||||||
|
queryBuilder //
|
||||||
|
.wildcard(wb -> wb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.wildcard("*") //
|
||||||
|
.boost(boost));
|
||||||
|
break;
|
||||||
|
case EQUALS:
|
||||||
|
queryBuilder.queryString(queryStringQuery(fieldName, searchText, Operator.And, boost));
|
||||||
|
break;
|
||||||
|
case CONTAINS:
|
||||||
|
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText + '*', true, boost));
|
||||||
|
break;
|
||||||
|
case STARTS_WITH:
|
||||||
|
queryBuilder.queryString(queryStringQuery(fieldName, searchText + '*', true, boost));
|
||||||
|
break;
|
||||||
|
case ENDS_WITH:
|
||||||
|
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
|
||||||
|
break;
|
||||||
|
case EXPRESSION:
|
||||||
|
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
|
||||||
|
break;
|
||||||
|
case LESS:
|
||||||
|
queryBuilder //
|
||||||
|
.range(rb -> rb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.lt(JsonData.of(value)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
break;
|
||||||
|
case LESS_EQUAL:
|
||||||
|
queryBuilder //
|
||||||
|
.range(rb -> rb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.lte(JsonData.of(value)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
break;
|
||||||
|
case GREATER:
|
||||||
|
queryBuilder //
|
||||||
|
.range(rb -> rb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.gt(JsonData.of(value)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
break;
|
||||||
|
case GREATER_EQUAL:
|
||||||
|
queryBuilder //
|
||||||
|
.range(rb -> rb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.gte(JsonData.of(value)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
break;
|
||||||
|
case BETWEEN:
|
||||||
|
Object[] ranges = (Object[]) value;
|
||||||
|
queryBuilder //
|
||||||
|
.range(rb -> {
|
||||||
|
rb.field(fieldName);
|
||||||
|
if (ranges[0] != null) {
|
||||||
|
rb.gte(JsonData.of(ranges[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranges[1] != null) {
|
||||||
|
rb.lte(JsonData.of(ranges[1]));
|
||||||
|
}
|
||||||
|
rb.boost(boost); //
|
||||||
|
return rb;
|
||||||
|
}); //
|
||||||
|
|
||||||
|
break;
|
||||||
|
case FUZZY:
|
||||||
|
queryBuilder //
|
||||||
|
.fuzzy(fb -> fb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.value(FieldValue.of(searchText)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
break;
|
||||||
|
case MATCHES:
|
||||||
|
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
|
||||||
|
break;
|
||||||
|
case MATCHES_ALL:
|
||||||
|
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case IN:
|
||||||
|
if (value instanceof Iterable) {
|
||||||
|
Iterable<?> iterable = (Iterable<?>) value;
|
||||||
|
if (isKeywordField) {
|
||||||
|
queryBuilder.bool(bb -> bb //
|
||||||
|
.must(mb -> mb //
|
||||||
|
.terms(tb -> tb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.terms(tsb -> tsb //
|
||||||
|
.value(toFieldValueList(iterable))))) //
|
||||||
|
.boost(boost)); //
|
||||||
|
} else {
|
||||||
|
queryBuilder //
|
||||||
|
.queryString(qsb -> qsb //
|
||||||
|
.fields(fieldName) //
|
||||||
|
.query(orQueryString(iterable)) //
|
||||||
|
.boost(boost)); //
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NOT_IN:
|
||||||
|
if (value instanceof Iterable) {
|
||||||
|
Iterable<?> iterable = (Iterable<?>) value;
|
||||||
|
if (isKeywordField) {
|
||||||
|
queryBuilder.bool(bb -> bb //
|
||||||
|
.mustNot(mnb -> mnb //
|
||||||
|
.terms(tb -> tb //
|
||||||
|
.field(fieldName) //
|
||||||
|
.terms(tsb -> tsb //
|
||||||
|
.value(toFieldValueList(iterable))))) //
|
||||||
|
.boost(boost)); //
|
||||||
|
} else {
|
||||||
|
queryBuilder //
|
||||||
|
.queryString(qsb -> qsb //
|
||||||
|
.fields(fieldName) //
|
||||||
|
.query("NOT(" + orQueryString(iterable) + ')') //
|
||||||
|
.boost(boost)); //
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new CriteriaQueryException("Could not build query for " + entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<FieldValue> toFieldValueList(Iterable<?> iterable) {
|
||||||
|
List<FieldValue> list = new ArrayList<>();
|
||||||
|
for (Object item : iterable) {
|
||||||
|
list.add(item != null ? FieldValue.of(item.toString()) : null);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String orQueryString(Iterable<?> iterable) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (Object item : iterable) {
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append('"');
|
||||||
|
sb.append(QueryParserUtil.escape(item.toString()));
|
||||||
|
sb.append('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.core.GetResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.MgetResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.explain.ExplanationDetail;
|
||||||
|
import co.elastic.clients.elasticsearch.core.get.GetResult;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.NestedIdentity;
|
||||||
|
import co.elastic.clients.json.JsonData;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Explanation;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentAdapter;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to adapt different Elasticsearch responses to a
|
||||||
|
* {@link org.springframework.data.elasticsearch.core.document.Document}
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
final class DocumentAdapters {
|
||||||
|
|
||||||
|
private static final Log LOGGER = LogFactory.getLog(DocumentAdapters.class);
|
||||||
|
|
||||||
|
private DocumentAdapters() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link SearchDocument} from a {@link Hit} returned by the Elasticsearch client.
|
||||||
|
*
|
||||||
|
* @param hit the hit object
|
||||||
|
* @param jsonpMapper to map JsonData objects
|
||||||
|
* @return the created {@link SearchDocument}
|
||||||
|
*/
|
||||||
|
public static SearchDocument from(Hit<?> hit, JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
|
Assert.notNull(hit, "hit must not be null");
|
||||||
|
|
||||||
|
Map<String, List<String>> highlightFields = hit.highlight();
|
||||||
|
|
||||||
|
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
|
||||||
|
hit.innerHits().forEach((name, innerHitsResult) -> {
|
||||||
|
// noinspection ReturnOfNull
|
||||||
|
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null,
|
||||||
|
searchDocument -> null, jsonpMapper));
|
||||||
|
});
|
||||||
|
|
||||||
|
NestedMetaData nestedMetaData = from(hit.nested());
|
||||||
|
|
||||||
|
// todo #1973 explanation
|
||||||
|
Explanation explanation = from(hit.explanation());
|
||||||
|
|
||||||
|
// todo #1973 matchedQueries
|
||||||
|
List<String> matchedQueries = null;
|
||||||
|
// todo #1973 documentFields
|
||||||
|
Map<String, List<Object>> documentFields = Collections.emptyMap();
|
||||||
|
|
||||||
|
Document document;
|
||||||
|
Object source = hit.source();
|
||||||
|
if (source == null) {
|
||||||
|
// Elasticsearch provides raw JsonData, so we build the fields into a JSON string
|
||||||
|
StringBuilder sb = new StringBuilder("{");
|
||||||
|
final boolean[] firstField = { true };
|
||||||
|
hit.fields().forEach((key, jsonData) -> {
|
||||||
|
if (!firstField[0]) {
|
||||||
|
sb.append(',');
|
||||||
|
}
|
||||||
|
sb.append('"').append(key).append("\":") //
|
||||||
|
.append(jsonData.toJson(jsonpMapper).toString());
|
||||||
|
firstField[0] = false;
|
||||||
|
});
|
||||||
|
sb.append('}');
|
||||||
|
document = Document.parse(sb.toString());
|
||||||
|
} else {
|
||||||
|
if (source instanceof EntityAsMap) {
|
||||||
|
document = Document.from((EntityAsMap) source);
|
||||||
|
} else if (source instanceof JsonData) {
|
||||||
|
JsonData jsonData = (JsonData) source;
|
||||||
|
document = Document.from(jsonData.to(EntityAsMap.class));
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (LOGGER.isWarnEnabled()) {
|
||||||
|
LOGGER.warn(String.format("Cannot map from type " + source.getClass().getName()));
|
||||||
|
}
|
||||||
|
document = Document.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.setIndex(hit.index());
|
||||||
|
document.setId(hit.id());
|
||||||
|
|
||||||
|
if (hit.version() != null) {
|
||||||
|
document.setVersion(hit.version());
|
||||||
|
}
|
||||||
|
document.setSeqNo(hit.seqNo() != null && hit.seqNo() >= 0 ? hit.seqNo() : -2); // -2 was the default value in the
|
||||||
|
// old client
|
||||||
|
document.setPrimaryTerm(hit.primaryTerm() != null && hit.primaryTerm() > 0 ? hit.primaryTerm() : 0);
|
||||||
|
|
||||||
|
float score = hit.score() != null ? hit.score().floatValue() : Float.NaN;
|
||||||
|
return new SearchDocumentAdapter(document, score, hit.sort().toArray(new String[0]), documentFields,
|
||||||
|
highlightFields, innerHits, nestedMetaData, explanation, matchedQueries, hit.routing());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Explanation from(@Nullable co.elastic.clients.elasticsearch.core.explain.Explanation explanation) {
|
||||||
|
|
||||||
|
if (explanation == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<Explanation> details = explanation.details().stream().map(DocumentAdapters::from).collect(Collectors.toList());
|
||||||
|
return new Explanation(true, (double) explanation.value(), explanation.description(), details);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Explanation from(ExplanationDetail explanationDetail) {
|
||||||
|
|
||||||
|
List<Explanation> details = explanationDetail.details().stream().map(DocumentAdapters::from)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Explanation(null, (double) explanationDetail.value(), explanationDetail.description(), details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static NestedMetaData from(@Nullable NestedIdentity nestedIdentity) {
|
||||||
|
|
||||||
|
if (nestedIdentity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
NestedMetaData child = from(nestedIdentity.nested());
|
||||||
|
return NestedMetaData.of(nestedIdentity.field(), nestedIdentity.offset(), child);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Document} from a {@link GetResponse} where the found document is contained as {@link EntityAsMap}.
|
||||||
|
*
|
||||||
|
* @param getResponse the response instance
|
||||||
|
* @return the Document
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Document from(GetResult<EntityAsMap> getResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(getResponse, "getResponse must not be null");
|
||||||
|
|
||||||
|
if (!getResponse.found()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Document document = getResponse.source() != null ? Document.from(getResponse.source()) : Document.create();
|
||||||
|
document.setIndex(getResponse.index());
|
||||||
|
document.setId(getResponse.id());
|
||||||
|
|
||||||
|
if (getResponse.version() != null) {
|
||||||
|
document.setVersion(getResponse.version());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getResponse.seqNo() != null) {
|
||||||
|
document.setSeqNo(getResponse.seqNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getResponse.primaryTerm() != null) {
|
||||||
|
document.setPrimaryTerm(getResponse.primaryTerm());
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a list of {@link MultiGetItem}s from a {@link MgetResponse} where the data is contained as
|
||||||
|
* {@link EntityAsMap} instances.
|
||||||
|
*
|
||||||
|
* @param mgetResponse the response instance
|
||||||
|
* @return list of multiget items
|
||||||
|
*/
|
||||||
|
public static List<MultiGetItem<Document>> from(MgetResponse<EntityAsMap> mgetResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(mgetResponse, "mgetResponse must not be null");
|
||||||
|
|
||||||
|
return mgetResponse.docs().stream() //
|
||||||
|
.map(itemResponse -> MultiGetItem.of( //
|
||||||
|
itemResponse.isFailure() ? null : from(itemResponse.result()), //
|
||||||
|
ResponseConverter.getFailure(itemResponse)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AggregationsContainer implementation for the Elasticsearch aggregations.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ElasticsearchAggregations implements AggregationsContainer<Map<String, Aggregate>> {
|
||||||
|
|
||||||
|
private final Map<String, Aggregate> aggregations;
|
||||||
|
|
||||||
|
public ElasticsearchAggregations(Map<String, Aggregate> aggregations) {
|
||||||
|
this.aggregations = aggregations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Aggregate> aggregations() {
|
||||||
|
return aggregations;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpEntityEnclosingRequest;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.HttpRequestInterceptor;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.HttpResponseInterceptor;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.entity.ByteArrayEntity;
|
||||||
|
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
import org.elasticsearch.client.RequestOptions;
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.elasticsearch.client.RestClientBuilder;
|
||||||
|
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.client.ClientLogger;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to create the different Elasticsearch clients
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public final class ElasticsearchClients {
|
||||||
|
/**
|
||||||
|
* Name of whose value can be used to correlate log messages for this request.
|
||||||
|
*/
|
||||||
|
private static final String LOG_ID_ATTRIBUTE = ElasticsearchClients.class.getName() + ".LOG_ID";
|
||||||
|
private static final String X_SPRING_DATA_ELASTICSEARCH_CLIENT = "X-SpringDataElasticsearch-Client";
|
||||||
|
private static final String IMPERATIVE_CLIENT = "imperative";
|
||||||
|
private static final String REACTIVE_CLIENT = "reactive";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ReactiveElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||||
|
* @return the {@link ReactiveElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ReactiveElasticsearchClient createReactive(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||||
|
|
||||||
|
return createReactive(getRestClient(clientConfiguration), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ReactiveElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||||
|
* @param transportOptions options to be added to each request.
|
||||||
|
* @return the {@link ReactiveElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ReactiveElasticsearchClient createReactive(ClientConfiguration clientConfiguration,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
|
||||||
|
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null!");
|
||||||
|
|
||||||
|
return createReactive(getRestClient(clientConfiguration), transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @param restClient the underlying {@link RestClient}
|
||||||
|
* @return the {@link ReactiveElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ReactiveElasticsearchClient createReactive(RestClient restClient) {
|
||||||
|
return createReactive(restClient, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link ReactiveElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @param restClient the underlying {@link RestClient}
|
||||||
|
* @param transportOptions options to be added to each request.
|
||||||
|
* @return the {@link ReactiveElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ReactiveElasticsearchClient createReactive(RestClient restClient,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
return new ReactiveElasticsearchClient(getElasticsearchTransport(restClient, REACTIVE_CLIENT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new imperative {@link ElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||||
|
* @return the {@link ElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration) {
|
||||||
|
return createImperative(getRestClient(clientConfiguration), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new imperative {@link ElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration options, must not be {@literal null}.
|
||||||
|
* @param transportOptions options to be added to each request.
|
||||||
|
* @return the {@link ElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ElasticsearchClient createImperative(ClientConfiguration clientConfiguration,
|
||||||
|
TransportOptions transportOptions) {
|
||||||
|
return createImperative(getRestClient(clientConfiguration), transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new imperative {@link ElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param restClient the RestClient to use
|
||||||
|
* @return the {@link ElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ElasticsearchClient createImperative(RestClient restClient) {
|
||||||
|
return createImperative(restClient, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new imperative {@link ElasticsearchClient}
|
||||||
|
*
|
||||||
|
* @param restClient the RestClient to use
|
||||||
|
* @param transportOptions options to be added to each request.
|
||||||
|
* @return the {@link ElasticsearchClient}
|
||||||
|
*/
|
||||||
|
public static ElasticsearchClient createImperative(RestClient restClient,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
|
||||||
|
Assert.notNull(restClient, "restClient must not be null");
|
||||||
|
|
||||||
|
ElasticsearchTransport transport = getElasticsearchTransport(restClient, IMPERATIVE_CLIENT, transportOptions);
|
||||||
|
|
||||||
|
return new AutoCloseableElasticsearchClient(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a low level {@link RestClient} for the given configuration.
|
||||||
|
*
|
||||||
|
* @param clientConfiguration must not be {@literal null}
|
||||||
|
* @return the {@link RestClient}
|
||||||
|
*/
|
||||||
|
public static RestClient getRestClient(ClientConfiguration clientConfiguration) {
|
||||||
|
return getRestClientBuilder(clientConfiguration).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RestClientBuilder getRestClientBuilder(ClientConfiguration clientConfiguration) {
|
||||||
|
HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream()
|
||||||
|
.map(HttpHost::create).toArray(HttpHost[]::new);
|
||||||
|
RestClientBuilder builder = RestClient.builder(httpHosts);
|
||||||
|
|
||||||
|
if (clientConfiguration.getPathPrefix() != null) {
|
||||||
|
builder.setPathPrefix(clientConfiguration.getPathPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = clientConfiguration.getDefaultHeaders();
|
||||||
|
|
||||||
|
if (!headers.isEmpty()) {
|
||||||
|
builder.setDefaultHeaders(toHeaderArray(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setHttpClientConfigCallback(clientBuilder -> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
|
||||||
|
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||||
|
|
||||||
|
if (!connectTimeout.isNegative()) {
|
||||||
|
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||||
|
|
||||||
|
if (!socketTimeout.isNegative()) {
|
||||||
|
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||||
|
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||||
|
|
||||||
|
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
|
||||||
|
|
||||||
|
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||||
|
.getClientConfigurers()) {
|
||||||
|
if (clientConfigurer instanceof ElasticsearchClientConfigurationCallback) {
|
||||||
|
ElasticsearchClientConfigurationCallback restClientConfigurationCallback = (ElasticsearchClientConfigurationCallback) clientConfigurer;
|
||||||
|
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientBuilder;
|
||||||
|
});
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ElasticsearchTransport getElasticsearchTransport(RestClient restClient, String clientType,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
|
||||||
|
TransportOptions.Builder transportOptionsBuilder = transportOptions != null ? transportOptions.toBuilder()
|
||||||
|
: new RestClientOptions(RequestOptions.DEFAULT).toBuilder();
|
||||||
|
TransportOptions transportOptionsWithHeader = transportOptionsBuilder
|
||||||
|
.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType).build();
|
||||||
|
|
||||||
|
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(),
|
||||||
|
transportOptionsWithHeader);
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> formattedHosts(List<InetSocketAddress> hosts, boolean useSsl) {
|
||||||
|
return hosts.stream().map(it -> (useSsl ? "https" : "http") + "://" + it.getHostString() + ":" + it.getPort())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static org.apache.http.Header[] toHeaderArray(HttpHeaders headers) {
|
||||||
|
return headers.entrySet().stream() //
|
||||||
|
.flatMap(entry -> entry.getValue().stream() //
|
||||||
|
.map(value -> new BasicHeader(entry.getKey(), value))) //
|
||||||
|
.toArray(org.apache.http.Header[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging interceptors for Elasticsearch client logging.
|
||||||
|
*
|
||||||
|
* @see ClientLogger
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(HttpRequest request, HttpContext context) throws IOException {
|
||||||
|
|
||||||
|
String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE);
|
||||||
|
|
||||||
|
if (logId == null) {
|
||||||
|
logId = ClientLogger.newLogId();
|
||||||
|
context.setAttribute(LOG_ID_ATTRIBUTE, logId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String headers = Arrays.stream(request.getAllHeaders())
|
||||||
|
.map(header -> header.getName()
|
||||||
|
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
|
||||||
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
|
||||||
|
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||||
|
|
||||||
|
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
|
||||||
|
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
entity.writeTo(buffer);
|
||||||
|
|
||||||
|
if (!entity.isRepeatable()) {
|
||||||
|
entityRequest.setEntity(new ByteArrayEntity(buffer.toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
|
||||||
|
headers, buffer::toString);
|
||||||
|
} else {
|
||||||
|
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
|
||||||
|
headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(HttpResponse response, HttpContext context) throws IOException {
|
||||||
|
|
||||||
|
String logId = (String) context.getAttribute(LOG_ID_ATTRIBUTE);
|
||||||
|
|
||||||
|
String headers = Arrays.stream(response.getAllHeaders())
|
||||||
|
.map(header -> header.getName()
|
||||||
|
+ ((header.getName().equals("Authorization")) ? ": *****" : ": " + header.getValue()))
|
||||||
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
|
|
||||||
|
// no way of logging the body, in this callback, it is not read yset, later there is no callback possibility in
|
||||||
|
// RestClient or RestClientTransport
|
||||||
|
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()), headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interceptor to inject custom supplied headers.
|
||||||
|
*
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||||
|
* the RestClient with a {@link HttpAsyncClientBuilder}
|
||||||
|
*
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public interface ElasticsearchClientConfigurationCallback
|
||||||
|
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||||
|
|
||||||
|
static ElasticsearchClientConfigurationCallback from(
|
||||||
|
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
|
||||||
|
|
||||||
|
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
|
||||||
|
|
||||||
|
// noinspection NullableProblems
|
||||||
|
return clientBuilderCallback::apply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||||
|
* the ReactiveElasticsearchClient with a {@link WebClient}
|
||||||
|
*
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
|
||||||
|
|
||||||
|
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
|
||||||
|
|
||||||
|
Assert.notNull(webClientCallback, "webClientCallback must not be null");
|
||||||
|
|
||||||
|
// noinspection NullableProblems
|
||||||
|
return webClientCallback::apply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.RequestOptions;
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||||
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||||
|
* connection using the Elasticsearch Client.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be implemented by deriving classes to provide the {@link ClientConfiguration}.
|
||||||
|
*
|
||||||
|
* @return configuration, must not be {@literal null}
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public abstract ClientConfiguration clientConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the underlying low level RestClient.
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration for the client, must not be {@literal null}
|
||||||
|
* @return RestClient
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RestClient restClient(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||||
|
|
||||||
|
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the {@link ElasticsearchClient} to be used.
|
||||||
|
*
|
||||||
|
* @param restClient the low level RestClient to use
|
||||||
|
* @return ElasticsearchClient instance
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
|
||||||
|
|
||||||
|
Assert.notNull(restClient, "restClient must not be null");
|
||||||
|
|
||||||
|
return ElasticsearchClients.createImperative(restClient, transportOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link ElasticsearchOperations} implementation using an
|
||||||
|
* {@link co.elastic.clients.elasticsearch.ElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @return never {@literal null}.
|
||||||
|
*/
|
||||||
|
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||||
|
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||||
|
ElasticsearchClient elasticsearchClient) {
|
||||||
|
|
||||||
|
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
|
||||||
|
template.setRefreshPolicy(refreshPolicy());
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the options that should be added to every request. Must not be {@literal null}
|
||||||
|
*/
|
||||||
|
public TransportOptions transportOptions() {
|
||||||
|
return new RestClientOptions(RequestOptions.DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
|
||||||
|
import co.elastic.clients.elasticsearch._types.ErrorResponse;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.dao.DataAccessResourceFailureException;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
|
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||||
|
import org.springframework.data.elasticsearch.RestStatusException;
|
||||||
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
|
||||||
|
|
||||||
|
private final JsonpMapper jsonpMapper;
|
||||||
|
|
||||||
|
public ElasticsearchExceptionTranslator(JsonpMapper jsonpMapper) {
|
||||||
|
this.jsonpMapper = jsonpMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
|
||||||
|
* RuntimeException
|
||||||
|
*
|
||||||
|
* @param throwable the Exception to map
|
||||||
|
* @return the potentially translated RuntimeException.
|
||||||
|
*/
|
||||||
|
public RuntimeException translateException(Throwable throwable) {
|
||||||
|
|
||||||
|
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
|
||||||
|
: new RuntimeException(throwable.getMessage(), throwable);
|
||||||
|
RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException);
|
||||||
|
|
||||||
|
return potentiallyTranslatedException != null ? potentiallyTranslatedException : runtimeException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
|
||||||
|
|
||||||
|
if (isSeqNoConflict(ex)) {
|
||||||
|
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo #1973 index unavailable?
|
||||||
|
|
||||||
|
if (ex instanceof ElasticsearchException) {
|
||||||
|
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
|
||||||
|
|
||||||
|
ErrorResponse response = elasticsearchException.response();
|
||||||
|
String body = JsonUtils.toJson(response, jsonpMapper);
|
||||||
|
|
||||||
|
return new UncategorizedElasticsearchException(ex.getMessage(), response.status(), body, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
if (cause instanceof IOException) {
|
||||||
|
return new DataAccessResourceFailureException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSeqNoConflict(Exception exception) {
|
||||||
|
// todo #1973 check if this works
|
||||||
|
Integer status = null;
|
||||||
|
String message = null;
|
||||||
|
|
||||||
|
if (exception instanceof RestStatusException) {
|
||||||
|
|
||||||
|
RestStatusException statusException = (RestStatusException) exception;
|
||||||
|
status = statusException.getStatus();
|
||||||
|
message = statusException.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != null && message != null) {
|
||||||
|
return status == 409 && message.contains("type=version_conflict_engine_exception")
|
||||||
|
&& message.contains("version conflict, required seqNo");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,496 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.Time;
|
||||||
|
import co.elastic.clients.elasticsearch.core.*;
|
||||||
|
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
import co.elastic.clients.transport.Version;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||||
|
import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation;
|
||||||
|
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
|
||||||
|
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||||
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
|
import org.springframework.data.elasticsearch.core.SearchScrollHits;
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||||
|
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.core.reindex.ReindexRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link org.springframework.data.elasticsearch.core.ElasticsearchOperations} using the new
|
||||||
|
* Elasticsearch client.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||||
|
|
||||||
|
private final ElasticsearchClient client;
|
||||||
|
private final RequestConverter requestConverter;
|
||||||
|
private final ResponseConverter responseConverter;
|
||||||
|
private final JsonpMapper jsonpMapper;
|
||||||
|
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||||
|
|
||||||
|
// region _initialization
|
||||||
|
public ElasticsearchTemplate(ElasticsearchClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
super(elasticsearchConverter);
|
||||||
|
|
||||||
|
Assert.notNull(client, "client must not be null");
|
||||||
|
|
||||||
|
this.client = client;
|
||||||
|
this.jsonpMapper = client._transport().jsonpMapper();
|
||||||
|
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
|
||||||
|
responseConverter = new ResponseConverter(jsonpMapper);
|
||||||
|
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractElasticsearchTemplate doCopy() {
|
||||||
|
return new ElasticsearchTemplate(client, elasticsearchConverter);
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region child templates
|
||||||
|
@Override
|
||||||
|
public IndexOperations indexOps(Class<?> clazz) {
|
||||||
|
return new IndicesTemplate(client.indices(), elasticsearchConverter, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IndexOperations indexOps(IndexCoordinates index) {
|
||||||
|
return new IndicesTemplate(client.indices(), elasticsearchConverter, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClusterOperations cluster() {
|
||||||
|
return new ClusterTemplate(client.cluster(), elasticsearchConverter);
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region document operations
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
GetRequest getRequest = requestConverter.documentGetRequest(elasticsearchConverter.convertId(id),
|
||||||
|
routingResolver.getRouting(), index, false);
|
||||||
|
GetResponse<EntityAsMap> getResponse = execute(client -> client.get(getRequest, EntityAsMap.class));
|
||||||
|
|
||||||
|
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
|
return callback.doWith(DocumentAdapters.from(getResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
|
||||||
|
MgetResponse<EntityAsMap> result = execute(client -> client.mget(request, EntityAsMap.class));
|
||||||
|
|
||||||
|
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
|
|
||||||
|
return DocumentAdapters.from(result).stream() //
|
||||||
|
.map(multiGetItem -> MultiGetItem.of( //
|
||||||
|
multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), multiGetItem.getFailure())) //
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
|
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, clazz, index,
|
||||||
|
getRefreshPolicy());
|
||||||
|
|
||||||
|
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
|
||||||
|
|
||||||
|
return responseConverter.byQueryResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String doIndex(IndexQuery query, IndexCoordinates indexCoordinates) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
|
||||||
|
IndexRequest<?> indexRequest = requestConverter.documentIndexRequest(query, indexCoordinates, refreshPolicy);
|
||||||
|
|
||||||
|
IndexResponse indexResponse = execute(client -> client.index(indexRequest));
|
||||||
|
|
||||||
|
Object queryObject = query.getObject();
|
||||||
|
|
||||||
|
if (queryObject != null) {
|
||||||
|
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.id(),
|
||||||
|
indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexResponse.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doExists(String id, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(id, "id must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
GetRequest request = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true);
|
||||||
|
|
||||||
|
return execute(client -> client.get(request, EntityAsMap.class)).found();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doDelete(String id, @Nullable String routing, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(id, "id must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
DeleteRequest request = requestConverter.documentDeleteRequest(elasticsearchConverter.convertId(id), routing, index,
|
||||||
|
getRefreshPolicy());
|
||||||
|
return execute(client -> client.delete(request)).id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReindexResponse reindex(ReindexRequest reindexRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest,
|
||||||
|
true);
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse = execute(
|
||||||
|
client -> client.reindex(reindexRequestES));
|
||||||
|
return responseConverter.reindexResponse(reindexResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String submitReindex(ReindexRequest reindexRequest) {
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest,
|
||||||
|
false);
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse = execute(
|
||||||
|
client -> client.reindex(reindexRequestES));
|
||||||
|
|
||||||
|
if (reindexResponse.task() == null) {
|
||||||
|
// todo #1973 check behaviour and create issue in ES if necessary
|
||||||
|
throw new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request");
|
||||||
|
}
|
||||||
|
|
||||||
|
return reindexResponse.task();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||||
|
IndexCoordinates index) {
|
||||||
|
|
||||||
|
BulkRequest bulkRequest = requestConverter.documentBulkRequest(queries, bulkOptions, index, refreshPolicy);
|
||||||
|
BulkResponse bulkResponse = execute(client -> client.bulk(bulkRequest));
|
||||||
|
List<IndexedObjectInformation> indexedObjectInformationList = checkForBulkOperationFailure(bulkResponse);
|
||||||
|
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
|
||||||
|
return indexedObjectInformationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getClusterVersion() {
|
||||||
|
return execute(client -> client.info().version().number());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getVendor() {
|
||||||
|
return "Elasticsearch";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getRuntimeLibraryVersion() {
|
||||||
|
return Version.VERSION.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// region search operations
|
||||||
|
@Override
|
||||||
|
public long count(Query query, @Nullable Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, true, false);
|
||||||
|
|
||||||
|
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||||
|
|
||||||
|
return searchResponse.hits().total().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> SearchHits<T> search(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, false);
|
||||||
|
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||||
|
|
||||||
|
ReadDocumentCallback<T> readDocumentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator = getEntityCreator(readDocumentCallback);
|
||||||
|
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||||
|
|
||||||
|
return callback.doWith(SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
return search(NativeQuery.builder() //
|
||||||
|
.withQuery(q -> q.moreLikeThis(requestConverter.moreLikeThisQuery(query, index)))//
|
||||||
|
.withPageable(query.getPageable()) //
|
||||||
|
.build(), clazz, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> SearchScrollHits<T> searchScrollStart(long scrollTimeInMillis, Query query, Class<T> clazz,
|
||||||
|
IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(query.getPageable(), "pageable of query must not be null.");
|
||||||
|
|
||||||
|
SearchRequest request = requestConverter.searchRequest(query, clazz, index, false, scrollTimeInMillis);
|
||||||
|
SearchResponse<EntityAsMap> response = execute(client -> client.search(request, EntityAsMap.class));
|
||||||
|
|
||||||
|
return getSearchScrollHits(clazz, index, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
|
||||||
|
IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(scrollId, "scrollId must not be null");
|
||||||
|
|
||||||
|
ScrollRequest request = ScrollRequest
|
||||||
|
.of(sr -> sr.scrollId(scrollId).scroll(Time.of(t -> t.time(scrollTimeInMillis + "ms"))));
|
||||||
|
ScrollResponse<EntityAsMap> response = execute(client -> client.scroll(request, EntityAsMap.class));
|
||||||
|
|
||||||
|
return getSearchScrollHits(clazz, index, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T, R extends SearchResponse<EntityAsMap>> SearchScrollHits<T> getSearchScrollHits(Class<T> clazz,
|
||||||
|
IndexCoordinates index, R response) {
|
||||||
|
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
|
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||||
|
index);
|
||||||
|
|
||||||
|
return callback
|
||||||
|
.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback), jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void searchScrollClear(List<String> scrollIds) {
|
||||||
|
|
||||||
|
Assert.notNull(scrollIds, "scrollIds must not be null");
|
||||||
|
|
||||||
|
if (!scrollIds.isEmpty()) {
|
||||||
|
ClearScrollRequest request = ClearScrollRequest.of(csr -> csr.scrollId(scrollIds));
|
||||||
|
execute(client -> client.clearScroll(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(queries, "queries must not be null");
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||||
|
for (Query query : queries) {
|
||||||
|
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection unchecked
|
||||||
|
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
|
|
||||||
|
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||||
|
Iterator<Class<?>> it = classes.iterator();
|
||||||
|
for (Query query : queries) {
|
||||||
|
Class<?> clazz = it.next();
|
||||||
|
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return doMultiSearch(multiSearchQueryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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");
|
||||||
|
|
||||||
|
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
|
||||||
|
Iterator<Class<?>> it = classes.iterator();
|
||||||
|
for (Query query : queries) {
|
||||||
|
Class<?> clazz = it.next();
|
||||||
|
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
return doMultiSearch(multiSearchQueryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* value class combining the information needed for a single query in a multisearch request.
|
||||||
|
*/
|
||||||
|
private static class MultiSearchQueryParameter {
|
||||||
|
final Query query;
|
||||||
|
final Class<?> clazz;
|
||||||
|
final IndexCoordinates index;
|
||||||
|
|
||||||
|
public MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
this.query = query;
|
||||||
|
this.clazz = clazz;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region client callback
|
||||||
|
/**
|
||||||
|
* Callback interface to be used with {@link #execute(ElasticsearchTemplate.ClientCallback)} for operating directly on
|
||||||
|
* the {@link ElasticsearchClient}.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ClientCallback<T> {
|
||||||
|
T doWithClient(ElasticsearchClient client) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a callback with the {@link ElasticsearchClient} and provide exception translation.
|
||||||
|
*
|
||||||
|
* @param callback the callback to execute, must not be {@literal null}
|
||||||
|
* @param <T> the type returned from the callback
|
||||||
|
* @return the callback result
|
||||||
|
*/
|
||||||
|
public <T> T execute(ElasticsearchTemplate.ClientCallback<T> callback) {
|
||||||
|
|
||||||
|
Assert.notNull(callback, "callback must not be null");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return callback.doWithClient(client);
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
throw exceptionTranslator.translateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region helper methods
|
||||||
|
@Override
|
||||||
|
public Query matchAllQuery() {
|
||||||
|
return NativeQuery.builder().withQuery(qb -> qb.matchAll(mab -> mab)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query idsQuery(List<String> ids) {
|
||||||
|
return NativeQuery.builder().withQuery(qb -> qb.ids(iq -> iq.values(ids))).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extract the list of {@link IndexedObjectInformation} from a {@link BulkResponse}.
|
||||||
|
*
|
||||||
|
* @param bulkResponse the response to evaluate
|
||||||
|
* @return the list of the {@link IndexedObjectInformation}s
|
||||||
|
*/
|
||||||
|
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
|
||||||
|
|
||||||
|
if (bulkResponse.errors()) {
|
||||||
|
Map<String, String> failedDocuments = new HashMap<>();
|
||||||
|
for (BulkResponseItem item : bulkResponse.items()) {
|
||||||
|
|
||||||
|
if (item.error() != null) {
|
||||||
|
failedDocuments.put(item.id(), item.error().reason());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BulkFailureException(
|
||||||
|
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||||
|
+ failedDocuments + ']',
|
||||||
|
failedDocuments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bulkResponse.items().stream()
|
||||||
|
.map(item -> IndexedObjectInformation.of(item.id(), item.seqNo(), item.primaryTerm(), item.version()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Map<String,Object> to represent any entity as it's returned from Elasticsearch and before it is converted to a
|
||||||
|
* {@link org.springframework.data.elasticsearch.core.document.Document}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class EntityAsMap extends DefaultStringObjectMap<EntityAsMap> {}
|
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters;
|
||||||
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the {@link Highlight} annotation from a method to an ElasticsearchClient
|
||||||
|
* {@link co.elastic.clients.elasticsearch.core.search.Highlight}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
class HighlightQueryBuilder {
|
||||||
|
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||||
|
|
||||||
|
HighlightQueryBuilder(
|
||||||
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||||
|
this.mappingContext = mappingContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
|
||||||
|
@Nullable Class<?> type) {
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.core.search.Highlight.Builder highlightBuilder = new co.elastic.clients.elasticsearch.core.search.Highlight.Builder();
|
||||||
|
|
||||||
|
// in the old implementation we could use one addParameters method, but in the new Elasticsearch client
|
||||||
|
// the builder for highlight and highlightfield share no code
|
||||||
|
addParameters(highlight.getParameters(), highlightBuilder);
|
||||||
|
|
||||||
|
for (HighlightField highlightField : highlight.getFields()) {
|
||||||
|
String mappedName = mapFieldName(highlightField.getName(), type);
|
||||||
|
highlightBuilder.fields(mappedName, hf -> {
|
||||||
|
addParameters(highlightField.getParameters(), hf, type);
|
||||||
|
return hf;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlightBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies
|
||||||
|
*/
|
||||||
|
private void addParameters(HighlightParameters parameters,
|
||||||
|
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) {
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
|
||||||
|
builder.boundaryChars(parameters.getBoundaryChars());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getBoundaryMaxScan() > -1) {
|
||||||
|
builder.boundaryMaxScan(parameters.getBoundaryMaxScan());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryScanner())) {
|
||||||
|
builder.boundaryScanner(boundaryScanner(parameters.getBoundaryScanner()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) {
|
||||||
|
builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getForceSource()) { // default is false
|
||||||
|
// todo #1973 parameter missing in new client
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getFragmenter())) {
|
||||||
|
builder.fragmenter(highlighterFragmenter(parameters.getFragmenter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getFragmentSize() > -1) {
|
||||||
|
builder.fragmentSize(parameters.getFragmentSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getNoMatchSize() > -1) {
|
||||||
|
builder.noMatchSize(parameters.getNoMatchSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getNumberOfFragments() > -1) {
|
||||||
|
builder.numberOfFragments(parameters.getNumberOfFragments());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getOrder())) {
|
||||||
|
builder.order(highlighterOrder(parameters.getOrder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPhraseLimit() > -1) {
|
||||||
|
// todo #1973 parameter missing in new client
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPreTags().length > 0) {
|
||||||
|
builder.preTags(Arrays.asList(parameters.getPreTags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPostTags().length > 0) {
|
||||||
|
builder.postTags(Arrays.asList(parameters.getPostTags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parameters.getRequireFieldMatch()) { // default is true
|
||||||
|
builder.requireFieldMatch(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getType())) {
|
||||||
|
builder.type(highlighterType(parameters.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getEncoder())) {
|
||||||
|
builder.encoder(highlighterEncoder(parameters.getEncoder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getTagsSchema())) {
|
||||||
|
builder.tagsSchema(highlighterTagsSchema(parameters.getTagsSchema()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* the builder for highlight and highlight fields don't share code, so we have these two methods here that basically are almost copies
|
||||||
|
*/
|
||||||
|
private void addParameters(HighlightFieldParameters parameters,
|
||||||
|
co.elastic.clients.elasticsearch.core.search.HighlightField.Builder builder, Class<?> type) {
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
|
||||||
|
builder.boundaryChars(parameters.getBoundaryChars());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getBoundaryMaxScan() > -1) {
|
||||||
|
builder.boundaryMaxScan(parameters.getBoundaryMaxScan());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryScanner())) {
|
||||||
|
builder.boundaryScanner(boundaryScanner(parameters.getBoundaryScanner()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) {
|
||||||
|
builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getForceSource()) { // default is false
|
||||||
|
builder.forceSource(parameters.getForceSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getFragmenter())) {
|
||||||
|
builder.fragmenter(highlighterFragmenter(parameters.getFragmenter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getFragmentSize() > -1) {
|
||||||
|
builder.fragmentSize(parameters.getFragmentSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getNoMatchSize() > -1) {
|
||||||
|
builder.noMatchSize(parameters.getNoMatchSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getNumberOfFragments() > -1) {
|
||||||
|
builder.numberOfFragments(parameters.getNumberOfFragments());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getOrder())) {
|
||||||
|
builder.order(highlighterOrder(parameters.getOrder()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPhraseLimit() > -1) {
|
||||||
|
builder.phraseLimit(parameters.getPhraseLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPreTags().length > 0) {
|
||||||
|
builder.preTags(Arrays.asList(parameters.getPreTags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getPostTags().length > 0) {
|
||||||
|
builder.postTags(Arrays.asList(parameters.getPostTags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parameters.getRequireFieldMatch()) { // default is true
|
||||||
|
builder.requireFieldMatch(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasLength(parameters.getType())) {
|
||||||
|
builder.type(highlighterType(parameters.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((parameters).getFragmentOffset() > -1) {
|
||||||
|
builder.fragmentOffset(parameters.getFragmentOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.getMatchedFields().length > 0) {
|
||||||
|
builder.matchedFields(Arrays.stream(parameters.getMatchedFields()).map(fieldName -> mapFieldName(fieldName, type)) //
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mapFieldName(String fieldName, @Nullable Class<?> type) {
|
||||||
|
|
||||||
|
if (type != null) {
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
|
||||||
|
|
||||||
|
if (persistentEntity != null) {
|
||||||
|
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
|
||||||
|
|
||||||
|
if (persistentProperty != null) {
|
||||||
|
return persistentProperty.getFieldName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.springframework.util.StringUtils.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.indices.*;
|
||||||
|
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexInformation;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.ResourceUtil;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.Settings;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the {@link IndexOperations} interface using en {@link ElasticsearchIndicesClient}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class IndicesTemplate extends ChildTemplate<ElasticsearchIndicesClient> implements IndexOperations {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(IndicesTemplate.class);
|
||||||
|
|
||||||
|
protected final ElasticsearchConverter elasticsearchConverter;
|
||||||
|
@Nullable protected final Class<?> boundClass;
|
||||||
|
@Nullable protected final IndexCoordinates boundIndex;
|
||||||
|
|
||||||
|
public IndicesTemplate(ElasticsearchIndicesClient client, ElasticsearchConverter elasticsearchConverter,
|
||||||
|
Class<?> boundClass) {
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
|
||||||
|
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null");
|
||||||
|
Assert.notNull(boundClass, "boundClass may not be null");
|
||||||
|
|
||||||
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
|
this.boundClass = boundClass;
|
||||||
|
this.boundIndex = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndicesTemplate(ElasticsearchIndicesClient client, ElasticsearchConverter elasticsearchConverter,
|
||||||
|
IndexCoordinates boundIndex) {
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
|
||||||
|
Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null");
|
||||||
|
Assert.notNull(boundIndex, "boundIndex must not be null");
|
||||||
|
|
||||||
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
|
this.boundClass = null;
|
||||||
|
this.boundIndex = boundIndex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Class<?> checkForBoundClass() {
|
||||||
|
if (boundClass == null) {
|
||||||
|
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||||
|
}
|
||||||
|
return boundClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean create() {
|
||||||
|
|
||||||
|
Settings settings = boundClass != null ? createSettings(boundClass) : new Settings();
|
||||||
|
return doCreate(getIndexCoordinates(), settings, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean create(Map<String, Object> settings) {
|
||||||
|
|
||||||
|
Assert.notNull(settings, "settings must not be null");
|
||||||
|
|
||||||
|
return doCreate(getIndexCoordinates(), settings, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean create(Map<String, Object> settings, Document mapping) {
|
||||||
|
|
||||||
|
Assert.notNull(settings, "settings must not be null");
|
||||||
|
Assert.notNull(mapping, "mapping must not be null");
|
||||||
|
|
||||||
|
return doCreate(getIndexCoordinates(), settings, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean createWithMapping() {
|
||||||
|
return doCreate(getIndexCoordinates(), createSettings(), createMapping());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
|
||||||
|
@Nullable Document mapping) {
|
||||||
|
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
Assert.notNull(settings, "settings must not be null");
|
||||||
|
|
||||||
|
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
|
||||||
|
CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest));
|
||||||
|
return Boolean.TRUE.equals(createIndexResponse.acknowledged());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete() {
|
||||||
|
return doDelete(getIndexCoordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doDelete(IndexCoordinates indexCoordinates) {
|
||||||
|
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
|
||||||
|
if (doExists(indexCoordinates)) {
|
||||||
|
DeleteIndexRequest deleteIndexRequest = requestConverter.indicesDeleteRequest(indexCoordinates);
|
||||||
|
DeleteIndexResponse deleteIndexResponse = execute(client -> client.delete(deleteIndexRequest));
|
||||||
|
return deleteIndexResponse.acknowledged();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists() {
|
||||||
|
return doExists(getIndexCoordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doExists(IndexCoordinates indexCoordinates) {
|
||||||
|
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
|
||||||
|
ExistsRequest existsRequest = requestConverter.indicesExistsRequest(indexCoordinates);
|
||||||
|
BooleanResponse existsResponse = execute(client -> client.exists(existsRequest));
|
||||||
|
return existsResponse.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
|
||||||
|
RefreshRequest refreshRequest = requestConverter.indicesRefreshRequest(getIndexCoordinates());
|
||||||
|
execute(client -> client.refresh(refreshRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document createMapping() {
|
||||||
|
return createMapping(checkForBoundClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document createMapping(Class<?> clazz) {
|
||||||
|
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
// load mapping specified in Mapping annotation if present
|
||||||
|
// noinspection DuplicatedCode
|
||||||
|
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||||
|
|
||||||
|
if (mappingAnnotation != null) {
|
||||||
|
String mappingPath = mappingAnnotation.mappingPath();
|
||||||
|
|
||||||
|
if (hasText(mappingPath)) {
|
||||||
|
String mappings = ResourceUtil.readFileFromClasspath(mappingPath);
|
||||||
|
|
||||||
|
if (hasText(mappings)) {
|
||||||
|
return Document.parse(mappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build mapping from field annotations
|
||||||
|
try {
|
||||||
|
String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz);
|
||||||
|
return Document.parse(mapping);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new UncategorizedElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean putMapping(Document mapping) {
|
||||||
|
|
||||||
|
Assert.notNull(mapping, "mapping must not be null");
|
||||||
|
|
||||||
|
PutMappingRequest putMappingRequest = requestConverter.indicesPutMappingRequest(getIndexCoordinates(), mapping);
|
||||||
|
PutMappingResponse putMappingResponse = execute(client -> client.putMapping(putMappingRequest));
|
||||||
|
return putMappingResponse.acknowledged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getMapping() {
|
||||||
|
|
||||||
|
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||||
|
GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(indexCoordinates);
|
||||||
|
GetMappingResponse getMappingResponse = execute(client -> client.getMapping(getMappingRequest));
|
||||||
|
|
||||||
|
Document mappingResponse = responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates);
|
||||||
|
return mappingResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings createSettings() {
|
||||||
|
return createSettings(checkForBoundClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings createSettings(Class<?> clazz) {
|
||||||
|
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(clazz);
|
||||||
|
String settingPath = persistentEntity.settingPath();
|
||||||
|
return hasText(settingPath) //
|
||||||
|
? Settings.parse(ResourceUtil.readFileFromClasspath(settingPath)) //
|
||||||
|
: persistentEntity.getDefaultSettings();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings getSettings() {
|
||||||
|
return getSettings(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Settings getSettings(boolean includeDefaults) {
|
||||||
|
|
||||||
|
GetIndicesSettingsRequest getIndicesSettingsRequest = requestConverter
|
||||||
|
.indicesGetSettingsRequest(getIndexCoordinates(), includeDefaults);
|
||||||
|
GetIndicesSettingsResponse getIndicesSettingsResponse = execute(
|
||||||
|
client -> client.getSettings(getIndicesSettingsRequest));
|
||||||
|
return responseConverter.indicesGetSettings(getIndicesSettingsResponse, getIndexCoordinates().getIndexName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean alias(AliasActions aliasActions) {
|
||||||
|
|
||||||
|
Assert.notNull(aliasActions, "aliasActions must not be null");
|
||||||
|
|
||||||
|
UpdateAliasesRequest updateAliasesRequest = requestConverter.indicesUpdateAliasesRequest(aliasActions);
|
||||||
|
UpdateAliasesResponse updateAliasesResponse = execute(client -> client.updateAliases(updateAliasesRequest));
|
||||||
|
return updateAliasesResponse.acknowledged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean putTemplate(PutTemplateRequest putTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.PutTemplateRequest putTemplateRequestES = requestConverter
|
||||||
|
.indicesPutTemplateRequest(putTemplateRequest);
|
||||||
|
return execute(client -> client.putTemplate(putTemplateRequestES)).acknowledged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter
|
||||||
|
.indicesGetTemplateRequest(getTemplateRequest);
|
||||||
|
GetTemplateResponse getTemplateResponse = execute(client -> client.getTemplate(getTemplateRequestES));
|
||||||
|
|
||||||
|
return responseConverter.indicesGetTemplateData(getTemplateResponse, getTemplateRequest.getTemplateName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest existsTemplateRequestSO = requestConverter
|
||||||
|
.indicesExistsTemplateRequest(existsTemplateRequest);
|
||||||
|
return execute(client -> client.existsTemplate(existsTemplateRequestSO)).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest deleteTemplateRequestES = requestConverter
|
||||||
|
.indicesDeleteTemplateRequest(deleteTemplateRequest);
|
||||||
|
return execute(client -> client.deleteTemplate(deleteTemplateRequestES)).acknowledged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IndexInformation> getInformation(IndexCoordinates indexCoordinates) {
|
||||||
|
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
|
||||||
|
GetIndexRequest getIndexRequest = requestConverter.indicesGetIndexRequest(indexCoordinates);
|
||||||
|
GetIndexResponse getIndexResponse = execute(client -> client.get(getIndexRequest));
|
||||||
|
return responseConverter.indicesGetIndexInformations(getIndexResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Helper functions
|
||||||
|
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
|
||||||
|
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IndexCoordinates getIndexCoordinates() {
|
||||||
|
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||||
|
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
import jakarta.json.stream.JsonGenerator;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
final class JsonUtils {
|
||||||
|
|
||||||
|
private static final Log LOGGER = LogFactory.getLog(JsonUtils.class);
|
||||||
|
|
||||||
|
private JsonUtils() {}
|
||||||
|
|
||||||
|
public static String toJson(Object object, JsonpMapper mapper) {
|
||||||
|
|
||||||
|
// noinspection SpellCheckingInspection
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
JsonGenerator generator = mapper.jsonProvider().createGenerator(baos);
|
||||||
|
mapper.serialize(object, generator);
|
||||||
|
generator.close();
|
||||||
|
String jsonMapping = "{}";
|
||||||
|
try {
|
||||||
|
jsonMapping = baos.toString("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
LOGGER.warn("could not read json", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonMapping;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Suggester;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new
|
||||||
|
* Elasticsearch Client library.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class NativeQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@Nullable private final Query query;
|
||||||
|
// note: the new client does not have pipeline aggs, these are just set up as normal aggs
|
||||||
|
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
|
||||||
|
@Nullable private Suggester suggester;
|
||||||
|
|
||||||
|
public NativeQuery(NativeQueryBuilder builder) {
|
||||||
|
super(builder);
|
||||||
|
this.query = builder.getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQuery(@Nullable Query query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NativeQueryBuilder builder() {
|
||||||
|
return new NativeQueryBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Query getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAggregation(String name, Aggregation aggregation) {
|
||||||
|
|
||||||
|
Assert.notNull(name, "name must not be null");
|
||||||
|
Assert.notNull(aggregation, "aggregation must not be null");
|
||||||
|
|
||||||
|
aggregations.put(name, aggregation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAggregations(Map<String, Aggregation> aggregations) {
|
||||||
|
|
||||||
|
Assert.notNull(aggregations, "aggregations must not be null");
|
||||||
|
|
||||||
|
this.aggregations.clear();
|
||||||
|
this.aggregations.putAll(aggregations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Aggregation> getAggregations() {
|
||||||
|
return aggregations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Suggester getSuggester() {
|
||||||
|
return suggester;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuggester(@Nullable Suggester suggester) {
|
||||||
|
this.suggester = suggester;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Suggester;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQueryBuilder> {
|
||||||
|
|
||||||
|
@Nullable private Query query;
|
||||||
|
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
|
||||||
|
@Nullable private Suggester suggester;
|
||||||
|
|
||||||
|
public NativeQueryBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Query getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQueryBuilder withQuery(Query query) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
|
this.query = query;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQueryBuilder withQuery(Function<Query.Builder, ObjectBuilder<Query>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return withQuery(fn.apply(new Query.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQueryBuilder withAggregation(String name, Aggregation aggregation) {
|
||||||
|
|
||||||
|
Assert.notNull(name, "name must not be null");
|
||||||
|
Assert.notNull(aggregation, "aggregation must not be null");
|
||||||
|
|
||||||
|
this.aggregations.put(name, aggregation);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQueryBuilder withSuggester(@Nullable Suggester suggester) {
|
||||||
|
this.suggester = suggester;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQuery build() {
|
||||||
|
NativeQuery nativeQuery = new NativeQuery(this);
|
||||||
|
nativeQuery.setAggregations(aggregations);
|
||||||
|
nativeQuery.setSuggester(suggester);
|
||||||
|
|
||||||
|
return nativeQuery;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||||
|
import co.elastic.clients.elasticsearch._types.LatLonGeoLocation;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.WrapperQuery;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class simplifying the creation of some more complex queries and type.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public final class QueryBuilders {
|
||||||
|
|
||||||
|
private QueryBuilders() {}
|
||||||
|
|
||||||
|
public static MatchQuery matchQuery(String fieldName, String query, @Nullable Operator operator,
|
||||||
|
@Nullable Float boost) {
|
||||||
|
|
||||||
|
Assert.notNull(fieldName, "fieldName must not be null");
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
|
return MatchQuery.of(mb -> mb.field(fieldName).query(FieldValue.of(query)).operator(operator).boost(boost));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query matchQueryAsQuery(String fieldName, String query, @Nullable Operator operator,
|
||||||
|
@Nullable Float boost) {
|
||||||
|
|
||||||
|
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.match(matchQuery(fieldName, query, operator, boost));
|
||||||
|
|
||||||
|
return builder.apply(new Query.Builder()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MatchAllQuery matchAllQuery() {
|
||||||
|
|
||||||
|
return MatchAllQuery.of(b -> b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query matchAllQueryAsQuery() {
|
||||||
|
|
||||||
|
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.matchAll(matchAllQuery());
|
||||||
|
|
||||||
|
return builder.apply(new Query.Builder()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Float boost) {
|
||||||
|
return queryStringQuery(fieldName, query, null, null, boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryStringQuery queryStringQuery(String fieldName, String query, Operator defaultOperator,
|
||||||
|
@Nullable Float boost) {
|
||||||
|
return queryStringQuery(fieldName, query, null, defaultOperator, boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
|
||||||
|
@Nullable Float boost) {
|
||||||
|
return queryStringQuery(fieldName, query, analyzeWildcard, null, boost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
|
||||||
|
@Nullable Operator defaultOperator, @Nullable Float boost) {
|
||||||
|
|
||||||
|
Assert.notNull(fieldName, "fieldName must not be null");
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
|
return QueryStringQuery.of(qs -> qs.fields(fieldName).query(query).analyzeWildcard(analyzeWildcard)
|
||||||
|
.defaultOperator(defaultOperator).boost(boost));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TermQuery termQuery(String fieldName, String value) {
|
||||||
|
|
||||||
|
Assert.notNull(fieldName, "fieldName must not be null");
|
||||||
|
Assert.notNull(value, "value must not be null");
|
||||||
|
|
||||||
|
return TermQuery.of(t -> t.field(fieldName).value(FieldValue.of(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query termQueryAsQuery(String fieldName, String value) {
|
||||||
|
|
||||||
|
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.term(termQuery(fieldName, value));
|
||||||
|
return builder.apply(new Query.Builder()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WildcardQuery wildcardQuery(String field, String value) {
|
||||||
|
|
||||||
|
Assert.notNull(field, "field must not be null");
|
||||||
|
Assert.notNull(value, "value must not be null");
|
||||||
|
|
||||||
|
return WildcardQuery.of(w -> w.field(field).wildcard(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query wildcardQueryAsQuery(String field, String value) {
|
||||||
|
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wildcard(wildcardQuery(field, value));
|
||||||
|
return builder.apply(new Query.Builder()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Query wrapperQueryAsQuery(String query) {
|
||||||
|
|
||||||
|
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wrapper(wrapperQuery(query));
|
||||||
|
|
||||||
|
return builder.apply(new Query.Builder()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrapperQuery wrapperQuery(String query) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
|
String encodedValue = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
return WrapperQuery.of(wq -> wq.query(encodedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LatLonGeoLocation latLon(GeoPoint geoPoint) {
|
||||||
|
|
||||||
|
Assert.notNull(geoPoint, "geoPoint must not be null");
|
||||||
|
|
||||||
|
return latLon(geoPoint.getLat(), geoPoint.getLon());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LatLonGeoLocation latLon(double lat, double lon) {
|
||||||
|
return LatLonGeoLocation.of(_0 -> _0.lat(lat).lon(lon));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.ApiClient;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base class for a reactive template that uses on of the {@link ReactiveElasticsearchClient}'s child clients.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveChildTemplate<CLIENT extends ApiClient> {
|
||||||
|
protected final CLIENT client;
|
||||||
|
protected final ElasticsearchConverter elasticsearchConverter;
|
||||||
|
protected final RequestConverter requestConverter;
|
||||||
|
protected final ResponseConverter responseConverter;
|
||||||
|
private final JsonpMapper jsonpMapper;
|
||||||
|
protected final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||||
|
|
||||||
|
public ReactiveChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
this.client = client;
|
||||||
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
|
jsonpMapper = client._transport().jsonpMapper();
|
||||||
|
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
|
||||||
|
responseConverter = new ResponseConverter(jsonpMapper);
|
||||||
|
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on the client.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ClientCallback<CLIENT, RESULT extends Publisher<?>> {
|
||||||
|
RESULT doWithClient(CLIENT client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a callback with the client and provide exception translation.
|
||||||
|
*
|
||||||
|
* @param callback the callback to execute, must not be {@literal null}
|
||||||
|
* @param <RESULT> the type returned from the callback
|
||||||
|
* @return the callback result
|
||||||
|
*/
|
||||||
|
public <RESULT> Publisher<RESULT> execute(ClientCallback<CLIENT, Publisher<RESULT>> callback) {
|
||||||
|
|
||||||
|
Assert.notNull(callback, "callback must not be null");
|
||||||
|
|
||||||
|
return Flux.defer(() -> callback.doWithClient(client)).onErrorMap(exceptionTranslator::translateException);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveClusterTemplate extends ReactiveChildTemplate<ReactiveElasticsearchClusterClient>
|
||||||
|
implements ReactiveClusterOperations {
|
||||||
|
|
||||||
|
public ReactiveClusterTemplate(ReactiveElasticsearchClusterClient client,
|
||||||
|
ElasticsearchConverter elasticsearchConverter) {
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ClusterHealth> health() {
|
||||||
|
|
||||||
|
HealthRequest healthRequest = requestConverter.clusterHealthRequest();
|
||||||
|
Mono<HealthResponse> healthResponse = Mono.from(execute(client -> client.health(healthRequest)));
|
||||||
|
return healthResponse.map(responseConverter::clusterHealth);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.ApiClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.ErrorResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.*;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.JsonEndpoint;
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||||
|
import co.elastic.clients.transport.endpoints.EndpointWithResponseMapperAttr;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive version of {@link co.elastic.clients.elasticsearch.ElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveElasticsearchClient extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchClient>
|
||||||
|
implements AutoCloseable {
|
||||||
|
|
||||||
|
public ReactiveElasticsearchClient(ElasticsearchTransport transport) {
|
||||||
|
super(transport, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveElasticsearchClient(ElasticsearchTransport transport, @Nullable TransportOptions transportOptions) {
|
||||||
|
super(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveElasticsearchClient withTransportOptions(@Nullable TransportOptions transportOptions) {
|
||||||
|
return new ReactiveElasticsearchClient(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// region child clients
|
||||||
|
|
||||||
|
public ReactiveElasticsearchClusterClient cluster() {
|
||||||
|
return new ReactiveElasticsearchClusterClient(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveElasticsearchIndicesClient indices() {
|
||||||
|
return new ReactiveElasticsearchIndicesClient(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
// region info
|
||||||
|
|
||||||
|
public Mono<InfoResponse> info() {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(InfoRequest._INSTANCE, InfoRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> ping() {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(PingRequest._INSTANCE, PingRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
// region document
|
||||||
|
|
||||||
|
public <T> Mono<IndexResponse> index(IndexRequest<T> request) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, IndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<IndexResponse> index(Function<IndexRequest.Builder<T>, ObjectBuilder<IndexRequest<T>>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return index(fn.apply(new IndexRequest.Builder<>()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BulkResponse> bulk(BulkRequest request) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, BulkRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BulkResponse> bulk(Function<BulkRequest.Builder, ObjectBuilder<BulkRequest>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return bulk(fn.apply(new BulkRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<GetResponse<T>> get(GetRequest request, Class<T> tClass) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
// code adapted from
|
||||||
|
// co.elastic.clients.elasticsearch.ElasticsearchClient.get(co.elastic.clients.elasticsearch.core.GetRequest,
|
||||||
|
// java.lang.Class<TDocument>)
|
||||||
|
// noinspection unchecked
|
||||||
|
JsonEndpoint<GetRequest, GetResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<GetRequest, GetResponse<T>, ErrorResponse>) GetRequest._ENDPOINT;
|
||||||
|
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.get.TDocument",
|
||||||
|
getDeserializer(tClass));
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<GetResponse<T>> get(Function<GetRequest.Builder, ObjectBuilder<GetRequest>> fn, Class<T> tClass) {
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return get(fn.apply(new GetRequest.Builder()).build(), tClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ReindexResponse> reindex(ReindexRequest request) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ReindexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ReindexResponse> reindex(Function<ReindexRequest.Builder, ObjectBuilder<ReindexRequest>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return reindex(fn.apply(new ReindexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteResponse> delete(DeleteRequest request) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DeleteRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteResponse> delete(Function<DeleteRequest.Builder, ObjectBuilder<DeleteRequest>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return delete(fn.apply(new DeleteRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
// region search
|
||||||
|
|
||||||
|
public <T> Mono<SearchResponse<T>> search(SearchRequest request, Class<T> tDocumentClass) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request,
|
||||||
|
SearchRequest.createSearchEndpoint(this.getDeserializer(tDocumentClass)), transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<SearchResponse<T>> search(Function<SearchRequest.Builder, ObjectBuilder<SearchRequest>> fn,
|
||||||
|
Class<T> tDocumentClass) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
|
||||||
|
|
||||||
|
return search(fn.apply(new SearchRequest.Builder()).build(), tDocumentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<ScrollResponse<T>> scroll(ScrollRequest request, Class<T> tDocumentClass) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
|
||||||
|
|
||||||
|
// code adapted from
|
||||||
|
// co.elastic.clients.elasticsearch.ElasticsearchClient.scroll(co.elastic.clients.elasticsearch.core.ScrollRequest,
|
||||||
|
// java.lang.Class<TDocument>)
|
||||||
|
// noinspection unchecked
|
||||||
|
JsonEndpoint<ScrollRequest, ScrollResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<ScrollRequest, ScrollResponse<T>, ErrorResponse>) ScrollRequest._ENDPOINT;
|
||||||
|
endpoint = new EndpointWithResponseMapperAttr<>(endpoint,
|
||||||
|
"co.elastic.clients:Deserializer:_global.scroll.TDocument", getDeserializer(tDocumentClass));
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Mono<ScrollResponse<T>> scroll(Function<ScrollRequest.Builder, ObjectBuilder<ScrollRequest>> fn,
|
||||||
|
Class<T> tDocumentClass) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
Assert.notNull(tDocumentClass, "tDocumentClass must not be null");
|
||||||
|
|
||||||
|
return scroll(fn.apply(new ScrollRequest.Builder()).build(), tDocumentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ClearScrollResponse> clearScroll(ClearScrollRequest request) {
|
||||||
|
|
||||||
|
Assert.notNull(request, "request must not be null");
|
||||||
|
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ClearScrollRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ClearScrollResponse> clearScroll(
|
||||||
|
Function<ClearScrollRequest.Builder, ObjectBuilder<ClearScrollRequest>> fn) {
|
||||||
|
|
||||||
|
Assert.notNull(fn, "fn must not be null");
|
||||||
|
|
||||||
|
return clearScroll(fn.apply(new ClearScrollRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.ApiClient;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive version of the {@link co.elastic.clients.elasticsearch.cluster.ElasticsearchClusterClient}
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveElasticsearchClusterClient
|
||||||
|
extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchClusterClient> {
|
||||||
|
|
||||||
|
public ReactiveElasticsearchClusterClient(ElasticsearchTransport transport,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
super(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveElasticsearchClusterClient withTransportOptions(@Nullable TransportOptions transportOptions) {
|
||||||
|
return new ReactiveElasticsearchClusterClient(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<HealthResponse> health(HealthRequest healthRequest) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(healthRequest, HealthRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<HealthResponse> health(Function<HealthRequest.Builder, ObjectBuilder<HealthRequest>> fn) {
|
||||||
|
return health(fn.apply(new HealthRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientOptions;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.RequestOptions;
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||||
|
* connection using the {@link ReactiveElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be implemented by deriving classes to provide the {@link ClientConfiguration}.
|
||||||
|
*
|
||||||
|
* @return configuration, must not be {@literal null}
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public abstract ClientConfiguration clientConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the underlying low level RestClient.
|
||||||
|
*
|
||||||
|
* @param clientConfiguration configuration for the client, must not be {@literal null}
|
||||||
|
* @return RestClient
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RestClient restClient(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||||
|
|
||||||
|
return ElasticsearchClients.getRestClient(clientConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the {@link ReactiveElasticsearchClient} instance used.
|
||||||
|
*
|
||||||
|
* @param restClient the low level RestClient to use
|
||||||
|
* @return ReactiveElasticsearchClient instance.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ReactiveElasticsearchClient reactiveElasticsearchClient(RestClient restClient) {
|
||||||
|
|
||||||
|
Assert.notNull(restClient, "restClient must not be null");
|
||||||
|
|
||||||
|
return ElasticsearchClients.createReactive(restClient, transportOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates {@link ReactiveElasticsearchOperations}.
|
||||||
|
*
|
||||||
|
* @return never {@literal null}.
|
||||||
|
*/
|
||||||
|
@Bean(name = { "reactiveElasticsearchOperations", "reactiveElasticsearchTemplate" })
|
||||||
|
public ReactiveElasticsearchOperations reactiveElasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||||
|
ReactiveElasticsearchClient reactiveElasticsearchClient) {
|
||||||
|
|
||||||
|
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
|
||||||
|
elasticsearchConverter);
|
||||||
|
template.setRefreshPolicy(refreshPolicy());
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the options that should be added to every request. Must not be {@literal null}
|
||||||
|
*/
|
||||||
|
public TransportOptions transportOptions() {
|
||||||
|
return new RestClientOptions(RequestOptions.DEFAULT).toBuilder().build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,629 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.ApiClient;
|
||||||
|
import co.elastic.clients.elasticsearch.indices.*;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.TransportOptions;
|
||||||
|
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||||
|
import co.elastic.clients.util.ObjectBuilder;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive version of the {@link co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient}
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveElasticsearchIndicesClient
|
||||||
|
extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchIndicesClient> {
|
||||||
|
|
||||||
|
public ReactiveElasticsearchIndicesClient(ElasticsearchTransport transport,
|
||||||
|
@Nullable TransportOptions transportOptions) {
|
||||||
|
super(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveElasticsearchIndicesClient withTransportOptions(@Nullable TransportOptions transportOptions) {
|
||||||
|
return new ReactiveElasticsearchIndicesClient(transport, transportOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AddBlockResponse> addBlock(AddBlockRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, AddBlockRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AddBlockResponse> addBlock(Function<AddBlockRequest.Builder, ObjectBuilder<AddBlockRequest>> fn) {
|
||||||
|
return addBlock(fn.apply(new AddBlockRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AnalyzeResponse> analyze(AnalyzeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, AnalyzeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AnalyzeResponse> analyze(Function<AnalyzeRequest.Builder, ObjectBuilder<AnalyzeRequest>> fn) {
|
||||||
|
return analyze(fn.apply(new AnalyzeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<AnalyzeResponse> analyze() {
|
||||||
|
return analyze(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ClearCacheResponse> clearCache(ClearCacheRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ClearCacheRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ClearCacheResponse> clearCache(Function<ClearCacheRequest.Builder, ObjectBuilder<ClearCacheRequest>> fn) {
|
||||||
|
return clearCache(fn.apply(new ClearCacheRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ClearCacheResponse> clearCache() {
|
||||||
|
return clearCache(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CloneIndexResponse> clone(CloneIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, CloneIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CloneIndexResponse> clone(Function<CloneIndexRequest.Builder, ObjectBuilder<CloneIndexRequest>> fn) {
|
||||||
|
return clone(fn.apply(new CloneIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CloseIndexResponse> close(CloseIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, CloseIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CloseIndexResponse> close(Function<CloseIndexRequest.Builder, ObjectBuilder<CloseIndexRequest>> fn) {
|
||||||
|
return close(fn.apply(new CloseIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CreateIndexResponse> create(CreateIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, CreateIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CreateIndexResponse> create(Function<CreateIndexRequest.Builder, ObjectBuilder<CreateIndexRequest>> fn) {
|
||||||
|
return create(fn.apply(new CreateIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CreateDataStreamResponse> createDataStream(CreateDataStreamRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, CreateDataStreamRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<CreateDataStreamResponse> createDataStream(
|
||||||
|
Function<CreateDataStreamRequest.Builder, ObjectBuilder<CreateDataStreamRequest>> fn) {
|
||||||
|
return createDataStream(fn.apply(new CreateDataStreamRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DataStreamsStatsResponse> dataStreamsStats(DataStreamsStatsRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DataStreamsStatsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DataStreamsStatsResponse> dataStreamsStats(
|
||||||
|
Function<DataStreamsStatsRequest.Builder, ObjectBuilder<DataStreamsStatsRequest>> fn) {
|
||||||
|
return dataStreamsStats(fn.apply(new DataStreamsStatsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DataStreamsStatsResponse> dataStreamsStats() {
|
||||||
|
return dataStreamsStats(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteIndexResponse> delete(DeleteIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DeleteIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteIndexResponse> delete(Function<DeleteIndexRequest.Builder, ObjectBuilder<DeleteIndexRequest>> fn) {
|
||||||
|
return delete(fn.apply(new DeleteIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteAliasResponse> deleteAlias(DeleteAliasRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DeleteAliasRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteAliasResponse> deleteAlias(
|
||||||
|
Function<DeleteAliasRequest.Builder, ObjectBuilder<DeleteAliasRequest>> fn) {
|
||||||
|
return deleteAlias(fn.apply(new DeleteAliasRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteDataStreamResponse> deleteDataStream(DeleteDataStreamRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DeleteDataStreamRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteDataStreamResponse> deleteDataStream(
|
||||||
|
Function<DeleteDataStreamRequest.Builder, ObjectBuilder<DeleteDataStreamRequest>> fn) {
|
||||||
|
return deleteDataStream(fn.apply(new DeleteDataStreamRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteIndexTemplateResponse> deleteIndexTemplate(DeleteIndexTemplateRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, DeleteIndexTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteIndexTemplateResponse> deleteIndexTemplate(
|
||||||
|
Function<DeleteIndexTemplateRequest.Builder, ObjectBuilder<DeleteIndexTemplateRequest>> fn) {
|
||||||
|
return deleteIndexTemplate(fn.apply(new DeleteIndexTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteTemplateResponse> deleteTemplate(DeleteTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DeleteTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DeleteTemplateResponse> deleteTemplate(
|
||||||
|
Function<DeleteTemplateRequest.Builder, ObjectBuilder<DeleteTemplateRequest>> fn) {
|
||||||
|
return deleteTemplate(fn.apply(new DeleteTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DiskUsageResponse> diskUsage(DiskUsageRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, DiskUsageRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<DiskUsageResponse> diskUsage(Function<DiskUsageRequest.Builder, ObjectBuilder<DiskUsageRequest>> fn) {
|
||||||
|
return diskUsage(fn.apply(new DiskUsageRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> exists(ExistsRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ExistsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> exists(Function<ExistsRequest.Builder, ObjectBuilder<ExistsRequest>> fn) {
|
||||||
|
return exists(fn.apply(new ExistsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsAlias(ExistsAliasRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ExistsAliasRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsAlias(Function<ExistsAliasRequest.Builder, ObjectBuilder<ExistsAliasRequest>> fn) {
|
||||||
|
return existsAlias(fn.apply(new ExistsAliasRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsIndexTemplate(ExistsIndexTemplateRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, ExistsIndexTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsIndexTemplate(
|
||||||
|
Function<ExistsIndexTemplateRequest.Builder, ObjectBuilder<ExistsIndexTemplateRequest>> fn) {
|
||||||
|
return existsIndexTemplate(fn.apply(new ExistsIndexTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsTemplate(ExistsTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ExistsTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsTemplate(
|
||||||
|
Function<ExistsTemplateRequest.Builder, ObjectBuilder<ExistsTemplateRequest>> fn) {
|
||||||
|
return existsTemplate(fn.apply(new ExistsTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsType(ExistsTypeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ExistsTypeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<BooleanResponse> existsType(Function<ExistsTypeRequest.Builder, ObjectBuilder<ExistsTypeRequest>> fn) {
|
||||||
|
return existsType(fn.apply(new ExistsTypeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushResponse> flush(FlushRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, FlushRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushResponse> flush(Function<FlushRequest.Builder, ObjectBuilder<FlushRequest>> fn) {
|
||||||
|
return flush(fn.apply(new FlushRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushResponse> flush() {
|
||||||
|
return flush(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushSyncedResponse> flushSynced(FlushSyncedRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, FlushSyncedRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushSyncedResponse> flushSynced(
|
||||||
|
Function<FlushSyncedRequest.Builder, ObjectBuilder<FlushSyncedRequest>> fn) {
|
||||||
|
return flushSynced(fn.apply(new FlushSyncedRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FlushSyncedResponse> flushSynced() {
|
||||||
|
return flushSynced(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
public Mono<ForcemergeResponse> forcemerge(ForcemergeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ForcemergeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
public Mono<ForcemergeResponse> forcemerge(Function<ForcemergeRequest.Builder, ObjectBuilder<ForcemergeRequest>> fn) {
|
||||||
|
return forcemerge(fn.apply(new ForcemergeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
public Mono<ForcemergeResponse> forcemerge() {
|
||||||
|
return forcemerge(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FreezeResponse> freeze(FreezeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, FreezeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<FreezeResponse> freeze(Function<FreezeRequest.Builder, ObjectBuilder<FreezeRequest>> fn) {
|
||||||
|
return freeze(fn.apply(new FreezeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndexResponse> get(GetIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndexResponse> get(Function<GetIndexRequest.Builder, ObjectBuilder<GetIndexRequest>> fn) {
|
||||||
|
return get(fn.apply(new GetIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetAliasResponse> getAlias(GetAliasRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetAliasRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetAliasResponse> getAlias(Function<GetAliasRequest.Builder, ObjectBuilder<GetAliasRequest>> fn) {
|
||||||
|
return getAlias(fn.apply(new GetAliasRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetAliasResponse> getAlias() {
|
||||||
|
return getAlias(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetDataStreamResponse> getDataStream(GetDataStreamRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetDataStreamRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetDataStreamResponse> getDataStream(
|
||||||
|
Function<GetDataStreamRequest.Builder, ObjectBuilder<GetDataStreamRequest>> fn) {
|
||||||
|
return getDataStream(fn.apply(new GetDataStreamRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetDataStreamResponse> getDataStream() {
|
||||||
|
return getDataStream(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetFieldMappingResponse> getFieldMapping(GetFieldMappingRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetFieldMappingRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetFieldMappingResponse> getFieldMapping(
|
||||||
|
Function<GetFieldMappingRequest.Builder, ObjectBuilder<GetFieldMappingRequest>> fn) {
|
||||||
|
return getFieldMapping(fn.apply(new GetFieldMappingRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndexTemplateResponse> getIndexTemplate(GetIndexTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetIndexTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndexTemplateResponse> getIndexTemplate(
|
||||||
|
Function<GetIndexTemplateRequest.Builder, ObjectBuilder<GetIndexTemplateRequest>> fn) {
|
||||||
|
return getIndexTemplate(fn.apply(new GetIndexTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndexTemplateResponse> getIndexTemplate() {
|
||||||
|
return getIndexTemplate(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetMappingResponse> getMapping(GetMappingRequest getMappingRequest) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(getMappingRequest, GetMappingRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetMappingResponse> getMapping(Function<GetMappingRequest.Builder, ObjectBuilder<GetMappingRequest>> fn) {
|
||||||
|
return getMapping(fn.apply(new GetMappingRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetMappingResponse> getMapping() {
|
||||||
|
return getMapping(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndicesSettingsResponse> getSettings(GetIndicesSettingsRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, GetIndicesSettingsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndicesSettingsResponse> getSettings(
|
||||||
|
Function<GetIndicesSettingsRequest.Builder, ObjectBuilder<GetIndicesSettingsRequest>> fn) {
|
||||||
|
return getSettings(fn.apply(new GetIndicesSettingsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetIndicesSettingsResponse> getSettings() {
|
||||||
|
return getSettings(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetTemplateResponse> getTemplate(GetTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetTemplateResponse> getTemplate(
|
||||||
|
Function<GetTemplateRequest.Builder, ObjectBuilder<GetTemplateRequest>> fn) {
|
||||||
|
return getTemplate(fn.apply(new GetTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetTemplateResponse> getTemplate() {
|
||||||
|
return getTemplate(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetUpgradeResponse> getUpgrade(GetUpgradeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, GetUpgradeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetUpgradeResponse> getUpgrade(Function<GetUpgradeRequest.Builder, ObjectBuilder<GetUpgradeRequest>> fn) {
|
||||||
|
return getUpgrade(fn.apply(new GetUpgradeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<GetUpgradeResponse> getUpgrade() {
|
||||||
|
return getUpgrade(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<MigrateToDataStreamResponse> migrateToDataStream(MigrateToDataStreamRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, MigrateToDataStreamRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<MigrateToDataStreamResponse> migrateToDataStream(
|
||||||
|
Function<MigrateToDataStreamRequest.Builder, ObjectBuilder<MigrateToDataStreamRequest>> fn) {
|
||||||
|
return migrateToDataStream(fn.apply(new MigrateToDataStreamRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<OpenResponse> open(OpenRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, OpenRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<OpenResponse> open(Function<OpenRequest.Builder, ObjectBuilder<OpenRequest>> fn) {
|
||||||
|
return open(fn.apply(new OpenRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PromoteDataStreamResponse> promoteDataStream(PromoteDataStreamRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, PromoteDataStreamRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PromoteDataStreamResponse> promoteDataStream(
|
||||||
|
Function<PromoteDataStreamRequest.Builder, ObjectBuilder<PromoteDataStreamRequest>> fn) {
|
||||||
|
return promoteDataStream(fn.apply(new PromoteDataStreamRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutAliasResponse> putAlias(PutAliasRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, PutAliasRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutAliasResponse> putAlias(Function<PutAliasRequest.Builder, ObjectBuilder<PutAliasRequest>> fn) {
|
||||||
|
return putAlias(fn.apply(new PutAliasRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutIndexTemplateResponse> putIndexTemplate(PutIndexTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, PutIndexTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutIndexTemplateResponse> putIndexTemplate(
|
||||||
|
Function<PutIndexTemplateRequest.Builder, ObjectBuilder<PutIndexTemplateRequest>> fn) {
|
||||||
|
return putIndexTemplate(fn.apply(new PutIndexTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutMappingResponse> putMapping(PutMappingRequest putMappingRequest) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(putMappingRequest, PutMappingRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutMappingResponse> putMapping(Function<PutMappingRequest.Builder, ObjectBuilder<PutMappingRequest>> fn) {
|
||||||
|
return putMapping(fn.apply(new PutMappingRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutIndicesSettingsResponse> putSettings(PutIndicesSettingsRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, PutIndicesSettingsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutIndicesSettingsResponse> putSettings(
|
||||||
|
Function<PutIndicesSettingsRequest.Builder, ObjectBuilder<PutIndicesSettingsRequest>> fn) {
|
||||||
|
return putSettings(fn.apply(new PutIndicesSettingsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutIndicesSettingsResponse> putSettings() {
|
||||||
|
return putSettings(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutTemplateResponse> putTemplate(PutTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, PutTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<PutTemplateResponse> putTemplate(
|
||||||
|
Function<PutTemplateRequest.Builder, ObjectBuilder<PutTemplateRequest>> fn) {
|
||||||
|
return putTemplate(fn.apply(new PutTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RecoveryResponse> recovery(RecoveryRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, RecoveryRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RecoveryResponse> recovery(Function<RecoveryRequest.Builder, ObjectBuilder<RecoveryRequest>> fn) {
|
||||||
|
return recovery(fn.apply(new RecoveryRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RecoveryResponse> recovery() {
|
||||||
|
return recovery(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RefreshResponse> refresh(RefreshRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, RefreshRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RefreshResponse> refresh(Function<RefreshRequest.Builder, ObjectBuilder<RefreshRequest>> fn) {
|
||||||
|
return refresh(fn.apply(new RefreshRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RefreshResponse> refresh() {
|
||||||
|
return refresh(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ReloadSearchAnalyzersResponse> reloadSearchAnalyzers(ReloadSearchAnalyzersRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, ReloadSearchAnalyzersRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ReloadSearchAnalyzersResponse> reloadSearchAnalyzers(
|
||||||
|
Function<ReloadSearchAnalyzersRequest.Builder, ObjectBuilder<ReloadSearchAnalyzersRequest>> fn) {
|
||||||
|
return reloadSearchAnalyzers(fn.apply(new ReloadSearchAnalyzersRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ResolveIndexResponse> resolveIndex(ResolveIndexRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ResolveIndexRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ResolveIndexResponse> resolveIndex(
|
||||||
|
Function<ResolveIndexRequest.Builder, ObjectBuilder<ResolveIndexRequest>> fn) {
|
||||||
|
return resolveIndex(fn.apply(new ResolveIndexRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RolloverResponse> rollover(RolloverRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, RolloverRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<RolloverResponse> rollover(Function<RolloverRequest.Builder, ObjectBuilder<RolloverRequest>> fn) {
|
||||||
|
return rollover(fn.apply(new RolloverRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SegmentsResponse> segments(SegmentsRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, SegmentsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SegmentsResponse> segments(Function<SegmentsRequest.Builder, ObjectBuilder<SegmentsRequest>> fn) {
|
||||||
|
return segments(fn.apply(new SegmentsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SegmentsResponse> segments() {
|
||||||
|
return segments(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ShardStoresResponse> shardStores(ShardStoresRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ShardStoresRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ShardStoresResponse> shardStores(
|
||||||
|
Function<ShardStoresRequest.Builder, ObjectBuilder<ShardStoresRequest>> fn) {
|
||||||
|
return shardStores(fn.apply(new ShardStoresRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ShardStoresResponse> shardStores() {
|
||||||
|
return shardStores(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ShrinkResponse> shrink(ShrinkRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ShrinkRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ShrinkResponse> shrink(Function<ShrinkRequest.Builder, ObjectBuilder<ShrinkRequest>> fn) {
|
||||||
|
return shrink(fn.apply(new ShrinkRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SimulateIndexTemplateResponse> simulateIndexTemplate(SimulateIndexTemplateRequest request) {
|
||||||
|
return Mono
|
||||||
|
.fromFuture(transport.performRequestAsync(request, SimulateIndexTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SimulateIndexTemplateResponse> simulateIndexTemplate(
|
||||||
|
Function<SimulateIndexTemplateRequest.Builder, ObjectBuilder<SimulateIndexTemplateRequest>> fn) {
|
||||||
|
return simulateIndexTemplate(fn.apply(new SimulateIndexTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SimulateTemplateResponse> simulateTemplate(SimulateTemplateRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, SimulateTemplateRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SimulateTemplateResponse> simulateTemplate(
|
||||||
|
Function<SimulateTemplateRequest.Builder, ObjectBuilder<SimulateTemplateRequest>> fn) {
|
||||||
|
return simulateTemplate(fn.apply(new SimulateTemplateRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SimulateTemplateResponse> simulateTemplate() {
|
||||||
|
return simulateTemplate(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SplitResponse> split(SplitRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, SplitRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<SplitResponse> split(Function<SplitRequest.Builder, ObjectBuilder<SplitRequest>> fn) {
|
||||||
|
return split(fn.apply(new SplitRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<IndicesStatsResponse> stats(IndicesStatsRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, IndicesStatsRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<IndicesStatsResponse> stats(
|
||||||
|
Function<IndicesStatsRequest.Builder, ObjectBuilder<IndicesStatsRequest>> fn) {
|
||||||
|
return stats(fn.apply(new IndicesStatsRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<IndicesStatsResponse> stats() {
|
||||||
|
return stats(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UnfreezeResponse> unfreeze(UnfreezeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, UnfreezeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UnfreezeResponse> unfreeze(Function<UnfreezeRequest.Builder, ObjectBuilder<UnfreezeRequest>> fn) {
|
||||||
|
return unfreeze(fn.apply(new UnfreezeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpdateAliasesResponse> updateAliases(UpdateAliasesRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, UpdateAliasesRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpdateAliasesResponse> updateAliases(
|
||||||
|
Function<UpdateAliasesRequest.Builder, ObjectBuilder<UpdateAliasesRequest>> fn) {
|
||||||
|
return updateAliases(fn.apply(new UpdateAliasesRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpdateAliasesResponse> updateAliases() {
|
||||||
|
return updateAliases(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpgradeResponse> upgrade(UpgradeRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, UpgradeRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpgradeResponse> upgrade(Function<UpgradeRequest.Builder, ObjectBuilder<UpgradeRequest>> fn) {
|
||||||
|
return upgrade(fn.apply(new UpgradeRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<UpgradeResponse> upgrade() {
|
||||||
|
return upgrade(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ValidateQueryResponse> validateQuery(ValidateQueryRequest request) {
|
||||||
|
return Mono.fromFuture(transport.performRequestAsync(request, ValidateQueryRequest._ENDPOINT, transportOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ValidateQueryResponse> validateQuery(
|
||||||
|
Function<ValidateQueryRequest.Builder, ObjectBuilder<ValidateQueryRequest>> fn) {
|
||||||
|
return validateQuery(fn.apply(new ValidateQueryRequest.Builder()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mono<ValidateQueryResponse> validateQuery() {
|
||||||
|
return validateQuery(builder -> builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,480 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.Result;
|
||||||
|
import co.elastic.clients.elasticsearch._types.Time;
|
||||||
|
import co.elastic.clients.elasticsearch.core.*;
|
||||||
|
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
import co.elastic.clients.transport.Version;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.util.function.Tuple2;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.elasticsearch.search.suggest.Suggest;
|
||||||
|
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||||
|
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||||
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
|
import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation;
|
||||||
|
import org.springframework.data.elasticsearch.client.util.ScrollState;
|
||||||
|
import org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate;
|
||||||
|
import org.springframework.data.elasticsearch.core.AggregationContainer;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
|
||||||
|
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations} using the new
|
||||||
|
* Elasticsearch client.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
|
||||||
|
|
||||||
|
private final ReactiveElasticsearchClient client;
|
||||||
|
private final RequestConverter requestConverter;
|
||||||
|
private final ResponseConverter responseConverter;
|
||||||
|
private final JsonpMapper jsonpMapper;
|
||||||
|
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||||
|
|
||||||
|
protected ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
|
||||||
|
super(converter);
|
||||||
|
|
||||||
|
Assert.notNull(client, "client must not be null");
|
||||||
|
|
||||||
|
this.client = client;
|
||||||
|
this.jsonpMapper = client._transport().jsonpMapper();
|
||||||
|
requestConverter = new RequestConverter(converter, jsonpMapper);
|
||||||
|
responseConverter = new ResponseConverter(jsonpMapper);
|
||||||
|
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Document operations
|
||||||
|
@Override
|
||||||
|
protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoordinates index) {
|
||||||
|
|
||||||
|
IndexRequest<?> indexRequest = requestConverter.documentIndexRequest(getIndexQuery(entity), index,
|
||||||
|
getRefreshPolicy());
|
||||||
|
return Mono.just(entity) //
|
||||||
|
.zipWith(//
|
||||||
|
Mono.from(execute((ClientCallback<Publisher<IndexResponse>>) client -> client.index(indexRequest))) //
|
||||||
|
.map(indexResponse -> new IndexResponseMetaData(indexResponse.id(), //
|
||||||
|
indexResponse.seqNo(), //
|
||||||
|
indexResponse.primaryTerm(), //
|
||||||
|
indexResponse.version() //
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(id, "id must not be null");
|
||||||
|
Assert.notNull(entityType, "entityType must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, false);
|
||||||
|
|
||||||
|
Mono<GetResponse<EntityAsMap>> getResponse = Mono.from(execute(
|
||||||
|
(ClientCallback<Publisher<GetResponse<EntityAsMap>>>) client -> client.get(getRequest, EntityAsMap.class)));
|
||||||
|
|
||||||
|
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||||
|
return getResponse.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ReindexResponse> reindex(ReindexRequest reindexRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest,
|
||||||
|
true);
|
||||||
|
|
||||||
|
return Mono.from(execute( //
|
||||||
|
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.ReindexResponse>>) client -> client
|
||||||
|
.reindex(reindexRequestES)))
|
||||||
|
.map(responseConverter::reindexResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> submitReindex(ReindexRequest reindexRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.core.ReindexRequest reindexRequestES = requestConverter.reindex(reindexRequest,
|
||||||
|
false);
|
||||||
|
|
||||||
|
return Mono.from(execute( //
|
||||||
|
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.ReindexResponse>>) client -> client
|
||||||
|
.reindex(reindexRequestES)))
|
||||||
|
.flatMap(response -> (response.task() == null)
|
||||||
|
? Mono.error( // todo #1973 check behaviour and create issue in ES if necessary
|
||||||
|
new UnsupportedBackendOperation("ElasticsearchClient did not return a task id on submit request"))
|
||||||
|
: Mono.just(response.task()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(queries, "List of UpdateQuery must not be null");
|
||||||
|
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||||
|
Assert.notNull(index, "Index must not be null");
|
||||||
|
|
||||||
|
return doBulkOperation(queries, bulkOptions, index).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Flux<BulkResponseItem> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||||
|
|
||||||
|
BulkRequest bulkRequest = requestConverter.documentBulkRequest(queries, bulkOptions, index, getRefreshPolicy());
|
||||||
|
return client.bulk(bulkRequest)
|
||||||
|
.onErrorMap(e -> new UncategorizedElasticsearchException("Error executing bulk request", e))
|
||||||
|
.flatMap(this::checkForBulkOperationFailure) //
|
||||||
|
.flatMapMany(response -> Flux.fromIterable(response.items()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private <R> Mono<BulkResponse> checkForBulkOperationFailure(BulkResponse bulkResponse) {
|
||||||
|
|
||||||
|
if (bulkResponse.errors()) {
|
||||||
|
Map<String, String> failedDocuments = new HashMap<>();
|
||||||
|
|
||||||
|
for (BulkResponseItem item : bulkResponse.items()) {
|
||||||
|
|
||||||
|
if (item.error() != null) {
|
||||||
|
failedDocuments.put(item.id(), item.error().reason());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BulkFailureException exception = new BulkFailureException(
|
||||||
|
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
|
||||||
|
+ failedDocuments + ']',
|
||||||
|
failedDocuments);
|
||||||
|
return Mono.error(exception);
|
||||||
|
} else {
|
||||||
|
return Mono.just(bulkResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(id, "id must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
return Mono.defer(() -> {
|
||||||
|
DeleteRequest deleteRequest = requestConverter.documentDeleteRequest(id, routing, index, getRefreshPolicy());
|
||||||
|
return doDelete(deleteRequest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<String> doDelete(DeleteRequest request) {
|
||||||
|
|
||||||
|
return Mono.from(execute((ClientCallback<Publisher<DeleteResponse>>) client -> client.delete(request))) //
|
||||||
|
.flatMap(deleteResponse -> {
|
||||||
|
if (deleteResponse.result() == Result.NotFound) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
return Mono.just(deleteResponse.id());
|
||||||
|
}).onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ReactiveElasticsearchTemplate doCopy() {
|
||||||
|
return new ReactiveElasticsearchTemplate(client, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
// region search operations
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
return Flux.defer(() -> {
|
||||||
|
boolean useScroll = !(query.getPageable().isPaged() || query.isLimiting());
|
||||||
|
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, useScroll);
|
||||||
|
|
||||||
|
if (useScroll) {
|
||||||
|
return doScroll(searchRequest);
|
||||||
|
} else {
|
||||||
|
return doFind(searchRequest);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Flux<SearchDocument> doScroll(SearchRequest searchRequest) {
|
||||||
|
|
||||||
|
Time scrollTimeout = searchRequest.scroll() != null ? searchRequest.scroll() : Time.of(t -> t.time("1m"));
|
||||||
|
|
||||||
|
Flux<SearchResponse<EntityAsMap>> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), //
|
||||||
|
state -> Mono
|
||||||
|
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client
|
||||||
|
.search(searchRequest, EntityAsMap.class))) //
|
||||||
|
.expand(entityAsMapSearchResponse -> {
|
||||||
|
|
||||||
|
state.updateScrollId(entityAsMapSearchResponse.scrollId());
|
||||||
|
|
||||||
|
if (entityAsMapSearchResponse.hits() == null
|
||||||
|
|| CollectionUtils.isEmpty(entityAsMapSearchResponse.hits().hits())) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mono.from(execute((ClientCallback<Publisher<ScrollResponse<EntityAsMap>>>) client -> client.scroll(
|
||||||
|
ScrollRequest.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout)), EntityAsMap.class)));
|
||||||
|
}),
|
||||||
|
this::cleanupScroll, (state, ex) -> cleanupScroll(state), this::cleanupScroll);
|
||||||
|
|
||||||
|
return searchResponses.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits())
|
||||||
|
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Publisher<?> cleanupScroll(ScrollState state) {
|
||||||
|
|
||||||
|
return execute((ClientCallback<Publisher<ClearScrollResponse>>) client -> client
|
||||||
|
.clearScroll(ClearScrollRequest.of(csr -> csr.scrollId(state.getScrollIds()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
SearchRequest searchRequest = requestConverter.searchRequest(query, entityType, index, true, false);
|
||||||
|
|
||||||
|
return Mono
|
||||||
|
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
|
||||||
|
EntityAsMap.class)))
|
||||||
|
.map(searchResponse -> searchResponse.hits().total() != null ? searchResponse.hits().total().value() : 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Flux<SearchDocument> doFind(SearchRequest searchRequest) {
|
||||||
|
|
||||||
|
return Mono
|
||||||
|
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
|
||||||
|
EntityAsMap.class))) //
|
||||||
|
.flatMapIterable(entityAsMapSearchResponse -> entityAsMapSearchResponse.hits().hits()) //
|
||||||
|
.map(entityAsMapHit -> DocumentAdapters.from(entityAsMapHit, jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
SearchRequest searchRequest = requestConverter.searchRequest(query, clazz, index, false, false);
|
||||||
|
|
||||||
|
// noinspection unchecked
|
||||||
|
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<T>((Class<T>) clazz, index);
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> callback.toEntity(searchDocument)
|
||||||
|
.toFuture();
|
||||||
|
|
||||||
|
return Mono
|
||||||
|
.from(execute((ClientCallback<Publisher<SearchResponse<EntityAsMap>>>) client -> client.search(searchRequest,
|
||||||
|
EntityAsMap.class)))
|
||||||
|
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<String> getVendor() {
|
||||||
|
return Mono.just("Elasticsearch");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<String> getRuntimeLibraryVersion() {
|
||||||
|
return Mono.just(Version.VERSION.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Mono<String> getClusterVersion() {
|
||||||
|
return Mono.from(execute(ReactiveElasticsearchClient::info)).map(infoResponse -> infoResponse.version().number());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!");
|
||||||
|
|
||||||
|
return entitiesPublisher //
|
||||||
|
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||||
|
.concatMap(entity -> maybeCallBeforeConvert(entity, index)) //
|
||||||
|
).collectList() //
|
||||||
|
.map(Entities::new) //
|
||||||
|
.flatMapMany(entities -> {
|
||||||
|
|
||||||
|
if (entities.isEmpty()) {
|
||||||
|
return Flux.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index)//
|
||||||
|
.index() //
|
||||||
|
.flatMap(indexAndResponse -> {
|
||||||
|
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||||
|
BulkResponseItem response = indexAndResponse.getT2();
|
||||||
|
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
|
||||||
|
response.primaryTerm(), response.version()));
|
||||||
|
return maybeCallAfterSave(savedEntity, index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ByQueryResponse> updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public <T> Publisher<T> execute(ReactiveElasticsearchOperations.ClientCallback<Publisher<T>> callback) {
|
||||||
|
throw new UnsupportedBackendOperation("direct execution on the WebClient is not supported for this class");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Publisher<T> executeWithIndicesClient(IndicesClientCallback<Publisher<T>> callback) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Publisher<T> executeWithClusterClient(ClusterClientCallback<Publisher<T>> callback) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
||||||
|
return new ReactiveIndicesTemplate(client.indices(), converter, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveIndexOperations indexOps(Class<?> clazz) {
|
||||||
|
return new ReactiveIndicesTemplate(client.indices(), converter, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveClusterOperations cluster() {
|
||||||
|
return new ReactiveClusterTemplate(client.cluster(), converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query matchAllQuery() {
|
||||||
|
return NativeQuery.builder().withQuery(QueryBuilders.matchAllQueryAsQuery()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query idsQuery(List<String> ids) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface to be used with {@link #execute(ReactiveElasticsearchOperations.ClientCallback)} for operating
|
||||||
|
* directly on {@link ReactiveElasticsearchClient}.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
interface ClientCallback<T extends Publisher<?>> {
|
||||||
|
|
||||||
|
T doWithClient(ReactiveElasticsearchClient client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a callback with the {@link ReactiveElasticsearchClient} and provide exception translation.
|
||||||
|
*
|
||||||
|
* @param callback the callback to execute, must not be {@literal null}
|
||||||
|
* @param <T> the type returned from the callback
|
||||||
|
* @return the callback result
|
||||||
|
*/
|
||||||
|
public <T> Publisher<T> execute(ReactiveElasticsearchTemplate.ClientCallback<Publisher<T>> callback) {
|
||||||
|
return Flux.defer(() -> callback.doWithClient(client)).onErrorMap(this::translateException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a
|
||||||
|
* RuntimeException
|
||||||
|
*
|
||||||
|
* @param throwable the Exception to map
|
||||||
|
* @return the potentially translated RuntimeException.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.springframework.util.StringUtils.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.AcknowledgedResponseBase;
|
||||||
|
import co.elastic.clients.elasticsearch.indices.*;
|
||||||
|
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
|
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.Mapping;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexInformation;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveResourceUtil;
|
||||||
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.ReactiveMappingBuilder;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.Settings;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
*/
|
||||||
|
public class ReactiveIndicesTemplate extends ReactiveChildTemplate<ReactiveElasticsearchIndicesClient>
|
||||||
|
implements ReactiveIndexOperations {
|
||||||
|
|
||||||
|
@Nullable private final Class<?> boundClass;
|
||||||
|
private final IndexCoordinates boundIndexCoordinates;
|
||||||
|
|
||||||
|
public ReactiveIndicesTemplate(ReactiveElasticsearchIndicesClient client,
|
||||||
|
ElasticsearchConverter elasticsearchConverter, IndexCoordinates index) {
|
||||||
|
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
this.boundClass = null;
|
||||||
|
this.boundIndexCoordinates = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveIndicesTemplate(ReactiveElasticsearchIndicesClient client,
|
||||||
|
ElasticsearchConverter elasticsearchConverter, Class<?> clazz) {
|
||||||
|
|
||||||
|
super(client, elasticsearchConverter);
|
||||||
|
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
this.boundClass = clazz;
|
||||||
|
this.boundIndexCoordinates = getIndexCoordinatesFor(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> create() {
|
||||||
|
|
||||||
|
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||||
|
|
||||||
|
if (boundClass != null) {
|
||||||
|
return createSettings(boundClass).flatMap(settings -> doCreate(indexCoordinates, settings, null));
|
||||||
|
} else {
|
||||||
|
return doCreate(indexCoordinates, new Settings(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> create(Map<String, Object> settings) {
|
||||||
|
|
||||||
|
Assert.notNull(settings, "settings must not be null");
|
||||||
|
|
||||||
|
return doCreate(getIndexCoordinates(), settings, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> create(Map<String, Object> settings, Document mapping) {
|
||||||
|
|
||||||
|
Assert.notNull(settings, "settings must not be null");
|
||||||
|
Assert.notNull(mapping, "mapping must not be null");
|
||||||
|
|
||||||
|
return doCreate(getIndexCoordinates(), settings, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> createWithMapping() {
|
||||||
|
return createSettings() //
|
||||||
|
.flatMap(settings -> //
|
||||||
|
createMapping().flatMap(mapping -> //
|
||||||
|
doCreate(getIndexCoordinates(), settings, mapping))); //
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Boolean> doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
|
||||||
|
@Nullable Document mapping) {
|
||||||
|
|
||||||
|
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
|
||||||
|
Mono<CreateIndexResponse> createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest)));
|
||||||
|
return createIndexResponse.map(CreateIndexResponse::acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> delete() {
|
||||||
|
return exists().flatMap(exists -> {
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
DeleteIndexRequest deleteIndexRequest = requestConverter.indicesDeleteRequest(getIndexCoordinates());
|
||||||
|
return Mono.from(execute(client -> client.delete(deleteIndexRequest))) //
|
||||||
|
.map(DeleteIndexResponse::acknowledged) //
|
||||||
|
.onErrorResume(NoSuchIndexException.class, e -> Mono.just(false));
|
||||||
|
} else {
|
||||||
|
return Mono.just(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> exists() {
|
||||||
|
|
||||||
|
ExistsRequest existsRequest = requestConverter.indicesExistsRequest(getIndexCoordinates());
|
||||||
|
Mono<BooleanResponse> existsResponse = Mono.from(execute(client -> client.exists(existsRequest)));
|
||||||
|
return existsResponse.map(BooleanResponse::value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> refresh() {
|
||||||
|
return Mono.from(execute(client1 -> client1.refresh())).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Document> createMapping() {
|
||||||
|
return createMapping(checkForBoundClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Document> createMapping(Class<?> clazz) {
|
||||||
|
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||||
|
|
||||||
|
if (mappingAnnotation != null) {
|
||||||
|
String mappingPath = mappingAnnotation.mappingPath();
|
||||||
|
|
||||||
|
if (hasText(mappingPath)) {
|
||||||
|
return ReactiveResourceUtil.loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReactiveMappingBuilder(elasticsearchConverter).buildReactivePropertyMapping(clazz).map(Document::parse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> putMapping(Mono<Document> mapping) {
|
||||||
|
|
||||||
|
Assert.notNull(mapping, "mapping must not be null");
|
||||||
|
|
||||||
|
Mono<PutMappingResponse> putMappingResponse = mapping
|
||||||
|
.map(document -> requestConverter.indicesPutMappingRequest(getIndexCoordinates(), document)) //
|
||||||
|
.flatMap(putMappingRequest -> Mono.from(client.putMapping(putMappingRequest)));
|
||||||
|
return putMappingResponse.map(PutMappingResponse::acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Document> getMapping() {
|
||||||
|
|
||||||
|
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||||
|
GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(indexCoordinates);
|
||||||
|
Mono<GetMappingResponse> getMappingResponse = Mono.from(execute(client -> client.getMapping(getMappingRequest)));
|
||||||
|
return getMappingResponse.map(response -> responseConverter.indicesGetMapping(response, indexCoordinates));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Settings> createSettings() {
|
||||||
|
return createSettings(checkForBoundClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Settings> createSettings(Class<?> clazz) {
|
||||||
|
|
||||||
|
Assert.notNull(clazz, "clazz must not be null");
|
||||||
|
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
|
||||||
|
.getRequiredPersistentEntity(clazz);
|
||||||
|
String settingPath = persistentEntity.settingPath();
|
||||||
|
return hasText(settingPath) //
|
||||||
|
? ReactiveResourceUtil.loadDocument(settingPath, "@Setting") //
|
||||||
|
.map(Settings::new) //
|
||||||
|
: Mono.just(persistentEntity.getDefaultSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Settings> getSettings(boolean includeDefaults) {
|
||||||
|
|
||||||
|
GetIndicesSettingsRequest getSettingsRequest = requestConverter.indicesGetSettingsRequest(getIndexCoordinates(),
|
||||||
|
includeDefaults);
|
||||||
|
Mono<GetIndicesSettingsResponse> getSettingsResponse = Mono
|
||||||
|
.from(execute(client -> client.getSettings(getSettingsRequest)));
|
||||||
|
return getSettingsResponse
|
||||||
|
.map(response -> responseConverter.indicesGetSettings(response, getIndexCoordinates().getIndexName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> alias(AliasActions aliasActions) {
|
||||||
|
|
||||||
|
Assert.notNull(aliasActions, "aliasActions must not be null");
|
||||||
|
|
||||||
|
UpdateAliasesRequest updateAliasesRequest = requestConverter.indicesUpdateAliasesRequest(aliasActions);
|
||||||
|
Mono<UpdateAliasesResponse> updateAliasesResponse = Mono
|
||||||
|
.from(execute(client -> client.updateAliases(updateAliasesRequest)));
|
||||||
|
return updateAliasesResponse.map(AcknowledgedResponseBase::acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Map<String, Set<AliasData>>> getAliases(String... aliasNames) {
|
||||||
|
return getAliases(aliasNames, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Map<String, Set<AliasData>>> getAliasesForIndex(String... indexNames) {
|
||||||
|
return getAliases(null, indexNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Map<String, Set<AliasData>>> getAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
|
||||||
|
|
||||||
|
GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(aliasNames, indexNames);
|
||||||
|
Mono<GetAliasResponse> getAliasResponse = Mono.from(execute(client -> client.getAlias(getAliasRequest)));
|
||||||
|
return getAliasResponse.map(responseConverter::indicesGetAliasData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.PutTemplateRequest putTemplateRequestES = requestConverter
|
||||||
|
.indicesPutTemplateRequest(putTemplateRequest);
|
||||||
|
Mono<PutTemplateResponse> putTemplateResponse = Mono
|
||||||
|
.from(execute(client -> client.putTemplate(putTemplateRequestES)));
|
||||||
|
return putTemplateResponse.map(PutTemplateResponse::acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<TemplateData> getTemplate(GetTemplateRequest getTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.GetTemplateRequest getTemplateRequestES = requestConverter
|
||||||
|
.indicesGetTemplateRequest(getTemplateRequest);
|
||||||
|
Mono<GetTemplateResponse> getTemplateResponse = Mono
|
||||||
|
.from(execute(client -> client.getTemplate(getTemplateRequestES)));
|
||||||
|
|
||||||
|
return getTemplateResponse.flatMap(response -> {
|
||||||
|
if (response != null) {
|
||||||
|
TemplateData templateData = responseConverter.indicesGetTemplateData(response,
|
||||||
|
getTemplateRequest.getTemplateName());
|
||||||
|
if (templateData != null) {
|
||||||
|
return Mono.just(templateData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Mono.empty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> existsTemplate(ExistsTemplateRequest existsTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.ExistsTemplateRequest existsTemplateRequestES = requestConverter
|
||||||
|
.indicesExistsTemplateRequest(existsTemplateRequest);
|
||||||
|
return Mono.from(execute(client1 -> client1.existsTemplate(existsTemplateRequestES))).map(BooleanResponse::value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) {
|
||||||
|
|
||||||
|
Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null");
|
||||||
|
|
||||||
|
co.elastic.clients.elasticsearch.indices.DeleteTemplateRequest deleteTemplateRequestES = requestConverter
|
||||||
|
.indicesDeleteTemplateRequest(deleteTemplateRequest);
|
||||||
|
return Mono.from(execute(client1 -> client1.deleteTemplate(deleteTemplateRequestES)))
|
||||||
|
.map(DeleteTemplateResponse::acknowledged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<IndexInformation> getInformation(IndexCoordinates index) {
|
||||||
|
throw new UnsupportedOperationException("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IndexCoordinates getIndexCoordinates() {
|
||||||
|
return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndexCoordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// region helper functions
|
||||||
|
private IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||||
|
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getIndexCoordinates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> checkForBoundClass() {
|
||||||
|
if (boundClass == null) {
|
||||||
|
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||||
|
}
|
||||||
|
return boundClass;
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,389 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
|
||||||
|
import co.elastic.clients.elasticsearch._types.ErrorCause;
|
||||||
|
import co.elastic.clients.elasticsearch._types.Time;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
|
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
|
||||||
|
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
|
||||||
|
import co.elastic.clients.elasticsearch.indices.*;
|
||||||
|
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
|
import org.springframework.data.elasticsearch.core.IndexInformation;
|
||||||
|
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||||
|
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.Settings;
|
||||||
|
import org.springframework.data.elasticsearch.core.index.TemplateData;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
|
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to convert Elasticsearch responses into Spring Data Elasticsearch classes.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
class ResponseConverter {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ResponseConverter.class);
|
||||||
|
|
||||||
|
private final JsonpMapper jsonpMapper;
|
||||||
|
|
||||||
|
public ResponseConverter(JsonpMapper jsonpMapper) {
|
||||||
|
this.jsonpMapper = jsonpMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// region cluster client
|
||||||
|
public ClusterHealth clusterHealth(HealthResponse healthResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(healthResponse, "healthResponse must not be null");
|
||||||
|
|
||||||
|
return ClusterHealth.builder() //
|
||||||
|
.withActivePrimaryShards(healthResponse.activePrimaryShards()) //
|
||||||
|
.withActiveShards(healthResponse.activeShards()) //
|
||||||
|
.withActiveShardsPercent(Double.parseDouble(healthResponse.activeShardsPercentAsNumber()))//
|
||||||
|
.withClusterName(healthResponse.clusterName()) //
|
||||||
|
.withDelayedUnassignedShards(healthResponse.delayedUnassignedShards()) //
|
||||||
|
.withInitializingShards(healthResponse.initializingShards()) //
|
||||||
|
.withNumberOfDataNodes(healthResponse.numberOfDataNodes()) //
|
||||||
|
.withNumberOfInFlightFetch(healthResponse.numberOfInFlightFetch()) //
|
||||||
|
.withNumberOfNodes(healthResponse.numberOfNodes()) //
|
||||||
|
.withNumberOfPendingTasks(healthResponse.numberOfPendingTasks()) //
|
||||||
|
.withRelocatingShards(healthResponse.relocatingShards()) //
|
||||||
|
.withStatus(healthResponse.status().toString()) //
|
||||||
|
.withTaskMaxWaitingTimeMillis(Long.parseLong(healthResponse.taskMaxWaitingInQueueMillis())) //
|
||||||
|
.withTimedOut(healthResponse.timedOut()) //
|
||||||
|
.withUnassignedShards(healthResponse.unassignedShards()) //
|
||||||
|
.build(); //
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region indices client
|
||||||
|
public Settings indicesGetSettings(GetIndicesSettingsResponse getIndicesSettingsResponse, String indexName) {
|
||||||
|
|
||||||
|
Assert.notNull(getIndicesSettingsResponse, "getIndicesSettingsResponse must not be null");
|
||||||
|
Assert.notNull(indexName, "indexName must not be null");
|
||||||
|
|
||||||
|
Settings settings = new Settings();
|
||||||
|
IndexState indexState = getIndicesSettingsResponse.get(indexName);
|
||||||
|
|
||||||
|
if (indexState != null) {
|
||||||
|
|
||||||
|
Function<IndexSettings, Settings> indexSettingsToSettings = indexSettings -> {
|
||||||
|
Settings parsedSettings = Settings.parse(toJson(indexSettings, jsonpMapper));
|
||||||
|
return (indexSettings.index() != null) ? parsedSettings : new Settings().append("index", parsedSettings);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (indexState.defaults() != null) {
|
||||||
|
Settings defaultSettings = indexSettingsToSettings.apply(indexState.defaults());
|
||||||
|
settings.merge(defaultSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexState.settings() != null) {
|
||||||
|
Settings nonDefaultSettings = indexSettingsToSettings.apply(indexState.settings());
|
||||||
|
settings.merge(nonDefaultSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document indicesGetMapping(GetMappingResponse getMappingResponse, IndexCoordinates indexCoordinates) {
|
||||||
|
|
||||||
|
Assert.notNull(getMappingResponse, "getMappingResponse must not be null");
|
||||||
|
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
|
||||||
|
|
||||||
|
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
|
||||||
|
|
||||||
|
if (mappings == null || mappings.size() == 0) {
|
||||||
|
return Document.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexMappingRecord indexMappingRecord = mappings.get(indexCoordinates.getIndexName());
|
||||||
|
|
||||||
|
// this can happen when the mapping was requested with an alias
|
||||||
|
if (indexMappingRecord == null) {
|
||||||
|
|
||||||
|
if (mappings.size() != 1) {
|
||||||
|
LOGGER.warn("no mapping returned for index {}", indexCoordinates.getIndexName());
|
||||||
|
return Document.create();
|
||||||
|
}
|
||||||
|
String index = mappings.keySet().iterator().next();
|
||||||
|
indexMappingRecord = mappings.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Document.parse(toJson(indexMappingRecord.mappings(), jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IndexInformation> indicesGetIndexInformations(GetIndexResponse getIndexResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(getIndexResponse, "getIndexResponse must not be null");
|
||||||
|
|
||||||
|
List<IndexInformation> indexInformationList = new ArrayList<>();
|
||||||
|
|
||||||
|
getIndexResponse.result().forEach((indexName, indexState) -> {
|
||||||
|
Settings settings = indexState.settings() != null ? Settings.parse(toJson(indexState.settings(), jsonpMapper))
|
||||||
|
: new Settings();
|
||||||
|
Document mappings = indexState.mappings() != null ? Document.parse(toJson(indexState.mappings(), jsonpMapper))
|
||||||
|
: Document.create();
|
||||||
|
|
||||||
|
List<AliasData> aliasDataList = new ArrayList<>();
|
||||||
|
indexState.aliases().forEach((aliasName, alias) -> aliasDataList.add(indicesGetAliasData(aliasName, alias)));
|
||||||
|
|
||||||
|
indexInformationList.add(IndexInformation.of(indexName, settings, mappings, aliasDataList));
|
||||||
|
|
||||||
|
});
|
||||||
|
return indexInformationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Set<AliasData>> indicesGetAliasData(GetAliasResponse getAliasResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(getAliasResponse, "getAliasResponse must not be null");
|
||||||
|
|
||||||
|
Map<String, Set<AliasData>> aliasDataMap = new HashMap<>();
|
||||||
|
getAliasResponse.result().forEach((indexName, alias) -> {
|
||||||
|
Set<AliasData> aliasDataSet = new HashSet<>();
|
||||||
|
alias.aliases()
|
||||||
|
.forEach((aliasName, aliasDefinition) -> aliasDataSet.add(indicesGetAliasData(aliasName, aliasDefinition)));
|
||||||
|
aliasDataMap.put(indexName, aliasDataSet);
|
||||||
|
});
|
||||||
|
return aliasDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AliasData indicesGetAliasData(String aliasName, Alias alias) {
|
||||||
|
Query filter = alias.filter();
|
||||||
|
String filterJson = filter != null ? toJson(filter, jsonpMapper) : null;
|
||||||
|
Document filterDocument = filterJson != null ? Document.parse(filterJson) : null;
|
||||||
|
return AliasData.of(aliasName, filterDocument, alias.indexRouting(), alias.searchRouting(), alias.isWriteIndex(),
|
||||||
|
alias.isHidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AliasData indicesGetAliasData(String aliasName, AliasDefinition alias) {
|
||||||
|
Query filter = alias.filter();
|
||||||
|
String filterJson = filter != null ? toJson(filter, jsonpMapper) : null;
|
||||||
|
Document filterDocument = filterJson != null ? Document.parse(filterJson) : null;
|
||||||
|
return AliasData.of(aliasName, filterDocument, alias.indexRouting(), alias.searchRouting(), alias.isWriteIndex(),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public TemplateData indicesGetTemplateData(GetTemplateResponse getTemplateResponse, String templateName) {
|
||||||
|
|
||||||
|
Assert.notNull(getTemplateResponse, "getTemplateResponse must not be null");
|
||||||
|
Assert.notNull(templateName, "templateName must not be null");
|
||||||
|
|
||||||
|
TemplateMapping templateMapping = getTemplateResponse.get(templateName);
|
||||||
|
if (templateMapping != null) {
|
||||||
|
|
||||||
|
Settings settings = new Settings();
|
||||||
|
templateMapping.settings().forEach((key, jsonData) -> {
|
||||||
|
|
||||||
|
if (key.contains(".")) {
|
||||||
|
// returned string contains " quotes
|
||||||
|
settings.put(key, jsonData.toJson().toString().replaceAll("^\"|\"$", ""));
|
||||||
|
} else {
|
||||||
|
settings.put(key, new DefaultStringObjectMap<>().fromJson(jsonData.toJson().toString()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Function<? super Map.Entry<String, Alias>, String> keyMapper = Map.Entry::getKey;
|
||||||
|
Function<? super Map.Entry<String, Alias>, AliasData> valueMapper = entry -> indicesGetAliasData(entry.getKey(),
|
||||||
|
entry.getValue());
|
||||||
|
|
||||||
|
Map<String, AliasData> aliases = templateMapping.aliases().entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(keyMapper, valueMapper));
|
||||||
|
|
||||||
|
Document mapping = Document.parse(toJson(templateMapping.mappings(), jsonpMapper));
|
||||||
|
|
||||||
|
TemplateData.TemplateDataBuilder builder = TemplateData.builder() //
|
||||||
|
.withIndexPatterns(templateMapping.indexPatterns().toArray(new String[0])) //
|
||||||
|
.withOrder(templateMapping.order()) //
|
||||||
|
.withSettings(settings) //
|
||||||
|
.withMapping(mapping) //
|
||||||
|
.withAliases(aliases) //
|
||||||
|
;
|
||||||
|
|
||||||
|
if (templateMapping.version() != null) {
|
||||||
|
builder.withVersion(templateMapping.version().intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region document operations
|
||||||
|
public ReindexResponse reindexResponse(co.elastic.clients.elasticsearch.core.ReindexResponse reindexResponse) {
|
||||||
|
|
||||||
|
Assert.notNull(reindexResponse, "reindexResponse must not be null");
|
||||||
|
|
||||||
|
List<ReindexResponse.Failure> failures = reindexResponse.failures() //
|
||||||
|
.stream() //
|
||||||
|
.map(this::reindexResponseFailureOf) //
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
return ReindexResponse.builder() //
|
||||||
|
.withTook(timeToLong(reindexResponse.took())) //
|
||||||
|
.withTimedOut(reindexResponse.timedOut()) //
|
||||||
|
.withTotal(reindexResponse.total()) //
|
||||||
|
.withCreated(reindexResponse.created()) //
|
||||||
|
.withUpdated(reindexResponse.updated()) //
|
||||||
|
.withDeleted(reindexResponse.deleted()) //
|
||||||
|
.withBatches(reindexResponse.batches()) //
|
||||||
|
.withVersionConflicts(reindexResponse.versionConflicts()) //
|
||||||
|
.withNoops(reindexResponse.noops()) //
|
||||||
|
.withBulkRetries(reindexResponse.retries().bulk()) //
|
||||||
|
.withSearchRetries(reindexResponse.retries().search()) //
|
||||||
|
.withThrottledMillis(Long.parseLong(reindexResponse.throttledMillis())) //
|
||||||
|
.withRequestsPerSecond(reindexResponse.requestsPerSecond()) //
|
||||||
|
.withThrottledUntilMillis(Long.parseLong(reindexResponse.throttledUntilMillis())).withFailures(failures) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReindexResponse.Failure reindexResponseFailureOf(BulkIndexByScrollFailure failure) {
|
||||||
|
return ReindexResponse.Failure.builder() //
|
||||||
|
.withIndex(failure.index()) //
|
||||||
|
.withType(failure.type()) //
|
||||||
|
.withId(failure.id()) //
|
||||||
|
.withStatus(failure.status())//
|
||||||
|
.withErrorCause(toErrorCause(failure.cause())) //
|
||||||
|
// seqno, term, aborted are not available in the new client
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByQueryResponse.Failure byQueryResponseFailureOf(BulkIndexByScrollFailure failure) {
|
||||||
|
return ByQueryResponse.Failure.builder() //
|
||||||
|
.withIndex(failure.index()) //
|
||||||
|
.withType(failure.type()) //
|
||||||
|
.withId(failure.id()) //
|
||||||
|
.withStatus(failure.status())//
|
||||||
|
.withErrorCause(toErrorCause(failure.cause())).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static MultiGetItem.Failure getFailure(MultiGetResponseItem<EntityAsMap> itemResponse) {
|
||||||
|
|
||||||
|
MultiGetError responseFailure = itemResponse.isFailure() ? itemResponse.failure() : null;
|
||||||
|
|
||||||
|
return responseFailure != null
|
||||||
|
? MultiGetItem.Failure.of(responseFailure.index(), null, responseFailure.id(), null,
|
||||||
|
toErrorCause(responseFailure.error()))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByQueryResponse byQueryResponse(DeleteByQueryResponse response) {
|
||||||
|
List<ByQueryResponse.Failure> failures = response.failures().stream().map(this::byQueryResponseFailureOf)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
ByQueryResponse.ByQueryResponseBuilder builder = ByQueryResponse.builder();
|
||||||
|
|
||||||
|
if (response.took() != null) {
|
||||||
|
builder.withTook(response.took());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.timedOut() != null) {
|
||||||
|
builder.withTimedOut(response.timedOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.total() != null) {
|
||||||
|
builder.withTotal(response.total());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.deleted() != null) {
|
||||||
|
builder.withDeleted(response.deleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.batches() != null) {
|
||||||
|
builder.withBatches(Math.toIntExact(response.batches()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.versionConflicts() != null) {
|
||||||
|
builder.withVersionConflicts(response.versionConflicts());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.noops() != null) {
|
||||||
|
builder.withNoops(response.noops());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.retries() != null) {
|
||||||
|
builder.withBulkRetries(response.retries().bulk());
|
||||||
|
builder.withSearchRetries(response.retries().search());
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.withFailures(failures);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region helper functions
|
||||||
|
private long timeToLong(Time time) {
|
||||||
|
|
||||||
|
if (time.isTime()) {
|
||||||
|
return Long.parseLong(time.time());
|
||||||
|
} else {
|
||||||
|
return time.offset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
|
||||||
|
|
||||||
|
if (errorCause != null) {
|
||||||
|
return new ElasticsearchErrorCause( //
|
||||||
|
errorCause.type(), //
|
||||||
|
errorCause.reason(), //
|
||||||
|
errorCause.stackTrace(), //
|
||||||
|
toErrorCause(errorCause.causedBy()), //
|
||||||
|
errorCause.rootCause().stream().map(ResponseConverter::toErrorCause).collect(Collectors.toList()), //
|
||||||
|
errorCause.suppressed().stream().map(ResponseConverter::toErrorCause).collect(Collectors.toList()));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Suggestion;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.TotalHits;
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.elasticsearch.search.SearchHits;
|
||||||
|
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class to create {@link SearchDocumentResponse} instances.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
class SearchDocumentResponseBuilder {
|
||||||
|
/**
|
||||||
|
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
||||||
|
*
|
||||||
|
* @param searchResponse the Elasticsearch search response
|
||||||
|
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
||||||
|
* @param jsonpMapper to map JsonData objects
|
||||||
|
* @return the SearchDocumentResponse
|
||||||
|
*/
|
||||||
|
public static <T> SearchDocumentResponse from(SearchResponse<EntityAsMap> searchResponse,
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
|
Assert.notNull(searchResponse, "searchResponse must not be null");
|
||||||
|
Assert.notNull(entityCreator, "entityCreator must not be null");
|
||||||
|
|
||||||
|
HitsMetadata<EntityAsMap> hitsMetadata = searchResponse.hits();
|
||||||
|
String scrollId = searchResponse.scrollId();
|
||||||
|
Map<String, Aggregate> aggregations = searchResponse.aggregations();
|
||||||
|
Map<String, List<Suggestion<EntityAsMap>>> suggest = searchResponse.suggest();
|
||||||
|
|
||||||
|
return from(hitsMetadata, scrollId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a {@link SearchDocumentResponseBuilder} from {@link SearchHits} with the given scrollId aggregations and
|
||||||
|
* suggestES
|
||||||
|
*
|
||||||
|
* @param <T> entity type
|
||||||
|
* @param hitsMetadata the {@link SearchHits} to process
|
||||||
|
* @param scrollId scrollId
|
||||||
|
* @param aggregations aggregations
|
||||||
|
* @param suggestES the suggestion response from Elasticsearch
|
||||||
|
* @param entityCreator function to create an entity from a {@link SearchDocument}, needed in mapping the suggest data
|
||||||
|
* @param jsonpMapper to map JsonData objects
|
||||||
|
* @return the {@link SearchDocumentResponse}
|
||||||
|
*/
|
||||||
|
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
|
||||||
|
Map<String, Aggregate> aggregations, Map<String, List<Suggestion<EntityAsMap>>> suggestES,
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
|
||||||
|
|
||||||
|
Assert.notNull(hitsMetadata, "hitsMetadata must not be null");
|
||||||
|
|
||||||
|
long totalHits;
|
||||||
|
String totalHitsRelation;
|
||||||
|
|
||||||
|
TotalHits responseTotalHits = hitsMetadata.total();
|
||||||
|
if (responseTotalHits != null) {
|
||||||
|
totalHits = responseTotalHits.value();
|
||||||
|
switch (responseTotalHits.relation().jsonValue()) {
|
||||||
|
case "eq":
|
||||||
|
totalHitsRelation = TotalHitsRelation.EQUAL_TO.name();
|
||||||
|
break;
|
||||||
|
case "gte":
|
||||||
|
totalHitsRelation = TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
totalHitsRelation = TotalHitsRelation.OFF.name();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalHits = hitsMetadata.hits().size();
|
||||||
|
totalHitsRelation = "OFF";
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxScore = hitsMetadata.maxScore() != null ? hitsMetadata.maxScore().floatValue() : Float.NaN;
|
||||||
|
|
||||||
|
List<SearchDocument> searchDocuments = new ArrayList<>();
|
||||||
|
for (Hit<?> hit : hitsMetadata.hits()) {
|
||||||
|
searchDocuments.add(DocumentAdapters.from(hit, jsonpMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// todo #1973
|
||||||
|
Suggest suggest = null;
|
||||||
|
|
||||||
|
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
|
||||||
|
aggregationsContainer, suggest);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch._types.Conflicts;
|
||||||
|
import co.elastic.clients.elasticsearch._types.DistanceUnit;
|
||||||
|
import co.elastic.clients.elasticsearch._types.GeoDistanceType;
|
||||||
|
import co.elastic.clients.elasticsearch._types.OpType;
|
||||||
|
import co.elastic.clients.elasticsearch._types.Refresh;
|
||||||
|
import co.elastic.clients.elasticsearch._types.SortMode;
|
||||||
|
import co.elastic.clients.elasticsearch._types.VersionType;
|
||||||
|
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.BuiltinHighlighterType;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HighlighterOrder;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HighlighterTagsSchema;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.HighlighterType;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Order;
|
||||||
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to handle new Elasticsearch client type values.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
final class TypeUtils {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static BoundaryScanner boundaryScanner(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "chars":
|
||||||
|
return BoundaryScanner.Chars;
|
||||||
|
case "sentence":
|
||||||
|
return BoundaryScanner.Sentence;
|
||||||
|
case "word":
|
||||||
|
return BoundaryScanner.Word;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Conflicts conflicts(ReindexRequest.Conflicts conflicts) {
|
||||||
|
switch (conflicts) {
|
||||||
|
case ABORT:
|
||||||
|
return Conflicts.Abort;
|
||||||
|
case PROCEED:
|
||||||
|
return Conflicts.Proceed;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Cannot map conflicts value " + conflicts.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static DistanceUnit distanceUnit(String unit) {
|
||||||
|
|
||||||
|
switch (unit.toLowerCase()) {
|
||||||
|
case "in":
|
||||||
|
case "inch":
|
||||||
|
return DistanceUnit.Inches;
|
||||||
|
case "yd":
|
||||||
|
case "yards":
|
||||||
|
return DistanceUnit.Yards;
|
||||||
|
case "ft":
|
||||||
|
case "feet":
|
||||||
|
return DistanceUnit.Feet;
|
||||||
|
case "km":
|
||||||
|
case "kilometers":
|
||||||
|
return DistanceUnit.Kilometers;
|
||||||
|
case "nm":
|
||||||
|
case "nmi":
|
||||||
|
return DistanceUnit.NauticMiles;
|
||||||
|
case "mm":
|
||||||
|
case "millimeters":
|
||||||
|
return DistanceUnit.Millimeters;
|
||||||
|
case "cm":
|
||||||
|
case "centimeters":
|
||||||
|
return DistanceUnit.Centimeters;
|
||||||
|
case "mi":
|
||||||
|
case "miles":
|
||||||
|
return DistanceUnit.Miles;
|
||||||
|
case "m":
|
||||||
|
case "meters":
|
||||||
|
return DistanceUnit.Meters;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static FieldType fieldType(String type) {
|
||||||
|
|
||||||
|
for (FieldType fieldType : FieldType.values()) {
|
||||||
|
|
||||||
|
if (fieldType.jsonValue().equals(type)) {
|
||||||
|
return fieldType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static GeoDistanceType geoDistanceType(GeoDistanceOrder.DistanceType distanceType) {
|
||||||
|
|
||||||
|
switch (distanceType) {
|
||||||
|
case arc:
|
||||||
|
return GeoDistanceType.Arc;
|
||||||
|
case plane:
|
||||||
|
return GeoDistanceType.Plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static HighlighterFragmenter highlighterFragmenter(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "simple":
|
||||||
|
return HighlighterFragmenter.Simple;
|
||||||
|
case "span":
|
||||||
|
return HighlighterFragmenter.Span;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static HighlighterOrder highlighterOrder(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
if ("score".equals(value.toLowerCase())) {
|
||||||
|
return HighlighterOrder.Score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static HighlighterType highlighterType(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "unified":
|
||||||
|
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Unified));
|
||||||
|
case "plain":
|
||||||
|
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.Plain));
|
||||||
|
case "fvh":
|
||||||
|
return HighlighterType.of(b -> b.builtin(BuiltinHighlighterType.FastVector));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static HighlighterEncoder highlighterEncoder(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
switch (value.toLowerCase()) {
|
||||||
|
case "default":
|
||||||
|
return HighlighterEncoder.Default;
|
||||||
|
case "html":
|
||||||
|
return HighlighterEncoder.Html;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static HighlighterTagsSchema highlighterTagsSchema(@Nullable String value) {
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
if ("styled".equals(value.toLowerCase())) {
|
||||||
|
return HighlighterTagsSchema.Styled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static OpType opType(@Nullable IndexQuery.OpType opType) {
|
||||||
|
|
||||||
|
if (opType != null) {
|
||||||
|
switch (opType) {
|
||||||
|
case INDEX:
|
||||||
|
return OpType.Index;
|
||||||
|
case CREATE:
|
||||||
|
return OpType.Create;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Refresh refresh(@Nullable RefreshPolicy refreshPolicy) {
|
||||||
|
|
||||||
|
if (refreshPolicy == null) {
|
||||||
|
return Refresh.False;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (refreshPolicy) {
|
||||||
|
case IMMEDIATE:
|
||||||
|
return Refresh.True;
|
||||||
|
case WAIT_UNTIL:
|
||||||
|
return Refresh.WaitFor;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
return Refresh.False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static SortMode sortMode(Order.Mode mode) {
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case min:
|
||||||
|
return SortMode.Min;
|
||||||
|
case max:
|
||||||
|
return SortMode.Max;
|
||||||
|
case median:
|
||||||
|
return SortMode.Median;
|
||||||
|
case avg:
|
||||||
|
return SortMode.Avg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static VersionType versionType(
|
||||||
|
@Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) {
|
||||||
|
|
||||||
|
if (versionType != null) {
|
||||||
|
switch (versionType) {
|
||||||
|
case INTERNAL:
|
||||||
|
return VersionType.Internal;
|
||||||
|
case EXTERNAL:
|
||||||
|
return VersionType.External;
|
||||||
|
case EXTERNAL_GTE:
|
||||||
|
return VersionType.ExternalGte;
|
||||||
|
case FORCE:
|
||||||
|
return VersionType.Force;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Integer waitForActiveShardsCount(@Nullable String value) {
|
||||||
|
// values taken from the RHLC implementation
|
||||||
|
if (value == null) {
|
||||||
|
return -2;
|
||||||
|
} else if ("all".equals(value.toUpperCase())) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Illegale value for waitForActiveShards" + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This package contains classes that use the new Elasticsearch client library (co.elastic.clients:elasticsearch-java)
|
||||||
|
* to access Elasticsearch.
|
||||||
|
*/
|
||||||
|
@org.springframework.lang.NonNullApi
|
||||||
|
@org.springframework.lang.NonNullFields
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
@ -15,33 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.reactive;
|
package org.springframework.data.elasticsearch.client.reactive;
|
||||||
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
|
||||||
import io.netty.handler.ssl.ClientAuth;
|
|
||||||
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
|
|
||||||
import io.netty.handler.ssl.JdkSslContext;
|
|
||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
|
||||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.netty.http.client.HttpClient;
|
|
||||||
import reactor.netty.transport.ProxyProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
|
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.ElasticsearchStatusException;
|
import org.elasticsearch.ElasticsearchStatusException;
|
||||||
@ -120,7 +106,6 @@ import org.springframework.data.util.Lazy;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
@ -229,7 +214,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
|||||||
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null");
|
Assert.notNull(clientConfiguration, "ClientConfiguration must not be null");
|
||||||
Assert.notNull(requestCreator, "RequestCreator must not be null");
|
Assert.notNull(requestCreator, "RequestCreator must not be null");
|
||||||
|
|
||||||
WebClientProvider provider = getWebClientProvider(clientConfiguration);
|
WebClientProvider provider = WebClientProvider.getWebClientProvider(clientConfiguration);
|
||||||
|
|
||||||
HostProvider<?> hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
|
HostProvider<?> hostProvider = HostProvider.provider(provider, clientConfiguration.getHeadersSupplier(),
|
||||||
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
|
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
|
||||||
@ -241,83 +226,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
|
|
||||||
|
|
||||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
|
||||||
Duration soTimeout = clientConfiguration.getSocketTimeout();
|
|
||||||
|
|
||||||
HttpClient httpClient = HttpClient.create().compress(true);
|
|
||||||
|
|
||||||
if (!connectTimeout.isNegative()) {
|
|
||||||
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!soTimeout.isNegative()) {
|
|
||||||
httpClient = httpClient.doOnConnected(connection -> connection //
|
|
||||||
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
|
||||||
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientConfiguration.getProxy().isPresent()) {
|
|
||||||
String proxy = clientConfiguration.getProxy().get();
|
|
||||||
String[] hostPort = proxy.split(":");
|
|
||||||
|
|
||||||
if (hostPort.length != 2) {
|
|
||||||
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
|
|
||||||
}
|
|
||||||
httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
|
||||||
.port(Integer.parseInt(hostPort[1])));
|
|
||||||
}
|
|
||||||
|
|
||||||
String scheme = "http";
|
|
||||||
|
|
||||||
if (clientConfiguration.useSsl()) {
|
|
||||||
|
|
||||||
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
|
||||||
|
|
||||||
if (sslContext.isPresent()) {
|
|
||||||
httpClient = httpClient
|
|
||||||
.secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null,
|
|
||||||
IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false)));
|
|
||||||
} else {
|
|
||||||
httpClient = httpClient.secure();
|
|
||||||
}
|
|
||||||
|
|
||||||
scheme = "https";
|
|
||||||
}
|
|
||||||
|
|
||||||
WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient));
|
|
||||||
|
|
||||||
if (clientConfiguration.getPathPrefix() != null) {
|
|
||||||
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
|
|
||||||
}
|
|
||||||
|
|
||||||
Function<WebClient, WebClient> webClientConfigurer = webClient -> {
|
|
||||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
|
||||||
.getClientConfigurers()) {
|
|
||||||
|
|
||||||
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
|
|
||||||
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
|
|
||||||
webClient = webClientConfigurationCallback.configure(webClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return webClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
provider = provider //
|
|
||||||
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
|
|
||||||
.withWebClientConfigurer(webClientConfigurer) //
|
|
||||||
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
|
|
||||||
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
|
|
||||||
|
|
||||||
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
|
|
||||||
httpHeaders.addAll(suppliedHeaders);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
|
public void setHeadersSupplier(Supplier<HttpHeaders> headersSupplier) {
|
||||||
|
|
||||||
Assert.notNull(headersSupplier, "headersSupplier must not be null");
|
Assert.notNull(headersSupplier, "headersSupplier must not be null");
|
||||||
|
@ -99,11 +99,34 @@ public interface HostProvider<T extends HostProvider<T>> {
|
|||||||
return lookupActiveHost(verification).map(this::createWebClient);
|
return lookupActiveHost(verification).map(this::createWebClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link WebClient} connecting to an active host utilizing cached {@link ElasticsearchHost}.
|
||||||
|
*
|
||||||
|
* @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none
|
||||||
|
* found.
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
default Mono<WebClient> getWebClient() {
|
||||||
|
return getWebClient(Verification.LAZY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link WebClient} connecting to an active host.
|
||||||
|
*
|
||||||
|
* @param verification must not be {@literal null}.
|
||||||
|
* @return the {@link Mono} emitting the client for an active host or {@link Mono#error(Throwable) an error} if none
|
||||||
|
* found.
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
default Mono<WebClient> getWebClient(Verification verification) {
|
||||||
|
return lookupActiveHost(verification).map(this::createWebClient);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link WebClient} for {@link InetSocketAddress endpoint}.
|
* Creates a {@link WebClient} for {@link InetSocketAddress endpoint}.
|
||||||
*
|
*
|
||||||
* @param endpoint must not be {@literal null}.
|
* @param endpoint must not be {@literal null}.
|
||||||
* @return a {@link WebClient} using the the given endpoint as {@literal base url}.
|
* @return a {@link WebClient} using the the given endpoint as {@literal transport url}.
|
||||||
*/
|
*/
|
||||||
WebClient createWebClient(InetSocketAddress endpoint);
|
WebClient createWebClient(InetSocketAddress endpoint);
|
||||||
|
|
||||||
|
@ -15,12 +15,30 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.client.reactive;
|
package org.springframework.data.elasticsearch.client.reactive;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
|
||||||
|
import io.netty.handler.ssl.JdkSslContext;
|
||||||
|
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
|
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||||
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
import reactor.netty.transport.ProxyProvider;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
@ -152,4 +170,96 @@ public interface WebClientProvider {
|
|||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
WebClientProvider withRequestConfigurer(Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer);
|
WebClientProvider withRequestConfigurer(Consumer<WebClient.RequestHeadersSpec<?>> requestConfigurer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link WebClientProvider} for the given configuration
|
||||||
|
*
|
||||||
|
* @param clientConfiguration must not be {@literal} null
|
||||||
|
* @return the {@link WebClientProvider}
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
static WebClientProvider getWebClientProvider(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
Assert.notNull(clientConfiguration, "clientConfiguration must not be null");
|
||||||
|
|
||||||
|
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||||
|
Duration soTimeout = clientConfiguration.getSocketTimeout();
|
||||||
|
|
||||||
|
HttpClient httpClient = HttpClient.create().compress(true);
|
||||||
|
|
||||||
|
if (!connectTimeout.isNegative()) {
|
||||||
|
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(connectTimeout.toMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!soTimeout.isNegative()) {
|
||||||
|
httpClient = httpClient.doOnConnected(connection -> connection //
|
||||||
|
.addHandlerLast(new ReadTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS))
|
||||||
|
.addHandlerLast(new WriteTimeoutHandler(soTimeout.toMillis(), TimeUnit.MILLISECONDS)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientConfiguration.getProxy().isPresent()) {
|
||||||
|
String proxy = clientConfiguration.getProxy().get();
|
||||||
|
String[] hostPort = proxy.split(":");
|
||||||
|
|
||||||
|
if (hostPort.length != 2) {
|
||||||
|
throw new IllegalArgumentException("invalid proxy configuration " + proxy + ", should be \"host:port\"");
|
||||||
|
}
|
||||||
|
httpClient = httpClient.proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.HTTP).host(hostPort[0])
|
||||||
|
.port(Integer.parseInt(hostPort[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
String scheme = "http";
|
||||||
|
|
||||||
|
if (clientConfiguration.useSsl()) {
|
||||||
|
|
||||||
|
Optional<SSLContext> sslContext = clientConfiguration.getSslContext();
|
||||||
|
|
||||||
|
if (sslContext.isPresent()) {
|
||||||
|
httpClient = httpClient
|
||||||
|
.secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null,
|
||||||
|
IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false)));
|
||||||
|
} else {
|
||||||
|
httpClient = httpClient.secure();
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme = "https";
|
||||||
|
}
|
||||||
|
|
||||||
|
WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient));
|
||||||
|
|
||||||
|
if (clientConfiguration.getPathPrefix() != null) {
|
||||||
|
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
Function<WebClient, WebClient> webClientConfigurer = webClient -> {
|
||||||
|
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||||
|
.getClientConfigurers()) {
|
||||||
|
|
||||||
|
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
|
||||||
|
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
|
||||||
|
webClient = webClientConfigurationCallback.configure(webClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientConfigurer instanceof ElasticsearchClients.WebClientConfigurationCallback) {
|
||||||
|
ElasticsearchClients.WebClientConfigurationCallback webClientConfigurationCallback = (ElasticsearchClients.WebClientConfigurationCallback) clientConfigurer;
|
||||||
|
webClient = webClientConfigurationCallback.configure(webClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return webClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
provider = provider //
|
||||||
|
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
|
||||||
|
.withWebClientConfigurer(webClientConfigurer) //
|
||||||
|
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec //
|
||||||
|
.headers(httpHeaders -> {
|
||||||
|
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
|
||||||
|
|
||||||
|
if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) {
|
||||||
|
httpHeaders.addAll(suppliedHeaders);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,16 +21,17 @@ import java.util.Collections;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.data.convert.EntityReader;
|
import org.springframework.data.convert.EntityReader;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.document.Document;
|
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.AfterConvertCallback;
|
||||||
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
|
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
|
||||||
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
|
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
|
||||||
@ -77,7 +78,6 @@ import org.springframework.util.Assert;
|
|||||||
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
|
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
|
||||||
|
|
||||||
protected ElasticsearchConverter elasticsearchConverter;
|
protected ElasticsearchConverter elasticsearchConverter;
|
||||||
protected RequestFactory requestFactory;
|
|
||||||
protected EntityOperations entityOperations;
|
protected EntityOperations entityOperations;
|
||||||
@Nullable protected EntityCallbacks entityCallbacks;
|
@Nullable protected EntityCallbacks entityCallbacks;
|
||||||
@Nullable protected RefreshPolicy refreshPolicy;
|
@Nullable protected RefreshPolicy refreshPolicy;
|
||||||
@ -96,8 +96,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
|||||||
this.entityOperations = new EntityOperations(mappingContext);
|
this.entityOperations = new EntityOperations(mappingContext);
|
||||||
this.routingResolver = new DefaultRoutingResolver(mappingContext);
|
this.routingResolver = new DefaultRoutingResolver(mappingContext);
|
||||||
|
|
||||||
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
|
||||||
|
|
||||||
// initialize the VersionInfo class in the initialization phase
|
// initialize the VersionInfo class in the initialization phase
|
||||||
// noinspection ResultOfMethodCallIgnored
|
// noinspection ResultOfMethodCallIgnored
|
||||||
VersionInfo.versionProperties();
|
VersionInfo.versionProperties();
|
||||||
@ -429,7 +427,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
|||||||
/*
|
/*
|
||||||
* internal use only, not for public API
|
* internal use only, not for public API
|
||||||
*/
|
*/
|
||||||
abstract protected <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
abstract protected <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis,
|
||||||
Class<T> clazz, IndexCoordinates index);
|
Class<T> clazz, IndexCoordinates index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -455,13 +453,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
|||||||
return elasticsearchConverter;
|
return elasticsearchConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 4.0
|
|
||||||
*/
|
|
||||||
public RequestFactory getRequestFactory() {
|
|
||||||
return requestFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String[] toArray(List<String> values) {
|
protected static String[] toArray(List<String> values) {
|
||||||
String[] valuesAsArray = new String[values.size()];
|
String[] valuesAsArray = new String[values.size()];
|
||||||
return values.toArray(valuesAsArray);
|
return values.toArray(valuesAsArray);
|
||||||
@ -551,9 +542,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
|||||||
@Nullable
|
@Nullable
|
||||||
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
|
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
|
||||||
|
|
||||||
EntityOperations.AdaptibleEntity<Object> adaptibleEntity = entityOperations.forEntity(entity,
|
EntityOperations.AdaptableEntity<Object> adaptableEntity = entityOperations.forEntity(entity,
|
||||||
elasticsearchConverter.getConversionService(), routingResolver);
|
elasticsearchConverter.getConversionService(), routingResolver);
|
||||||
return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null;
|
return adaptableEntity.hasSeqNoPrimaryTerm() ? adaptableEntity.getSeqNoPrimaryTerm() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> IndexQuery getIndexQuery(T entity) {
|
private <T> IndexQuery getIndexQuery(T entity) {
|
||||||
@ -588,6 +579,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
|
||||||
|
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tries to extract the version of the Elasticsearch cluster
|
* tries to extract the version of the Elasticsearch cluster
|
||||||
*
|
*
|
||||||
|
@ -45,8 +45,6 @@ import org.springframework.util.Assert;
|
|||||||
public abstract class AbstractIndexTemplate implements IndexOperations {
|
public abstract class AbstractIndexTemplate implements IndexOperations {
|
||||||
|
|
||||||
protected final ElasticsearchConverter elasticsearchConverter;
|
protected final ElasticsearchConverter elasticsearchConverter;
|
||||||
protected final RequestFactory requestFactory;
|
|
||||||
|
|
||||||
@Nullable protected final Class<?> boundClass;
|
@Nullable protected final Class<?> boundClass;
|
||||||
@Nullable private final IndexCoordinates boundIndex;
|
@Nullable private final IndexCoordinates boundIndex;
|
||||||
|
|
||||||
@ -55,7 +53,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
|||||||
Assert.notNull(boundClass, "boundClass may not be null");
|
Assert.notNull(boundClass, "boundClass may not be null");
|
||||||
|
|
||||||
this.elasticsearchConverter = elasticsearchConverter;
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
|
||||||
this.boundClass = boundClass;
|
this.boundClass = boundClass;
|
||||||
this.boundIndex = null;
|
this.boundIndex = null;
|
||||||
}
|
}
|
||||||
@ -65,7 +62,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
|||||||
Assert.notNull(boundIndex, "boundIndex may not be null");
|
Assert.notNull(boundIndex, "boundIndex may not be null");
|
||||||
|
|
||||||
this.elasticsearchConverter = elasticsearchConverter;
|
this.elasticsearchConverter = elasticsearchConverter;
|
||||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
|
||||||
this.boundClass = null;
|
this.boundClass = null;
|
||||||
this.boundIndex = boundIndex;
|
this.boundIndex = boundIndex;
|
||||||
}
|
}
|
||||||
@ -195,12 +191,9 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Document createMapping(Class<?> clazz) {
|
public Document createMapping(Class<?> clazz) {
|
||||||
return buildMapping(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Document buildMapping(Class<?> clazz) {
|
|
||||||
|
|
||||||
// load mapping specified in Mapping annotation if present
|
// load mapping specified in Mapping annotation if present
|
||||||
|
// noinspection DuplicatedCode
|
||||||
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||||
|
|
||||||
if (mappingAnnotation != null) {
|
if (mappingAnnotation != null) {
|
||||||
|
@ -0,0 +1,685 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.util.function.Tuple2;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
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.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.SearchDocument;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
|
||||||
|
import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback;
|
||||||
|
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;
|
||||||
|
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.Query;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||||
|
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
||||||
|
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||||
|
import org.springframework.data.elasticsearch.support.VersionInfo;
|
||||||
|
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||||
|
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class keeping common code for implementations of the {@link ReactiveElasticsearchOperations} interface
|
||||||
|
* independent of the used client.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
abstract public class AbstractReactiveElasticsearchTemplate
|
||||||
|
implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
||||||
|
|
||||||
|
protected static final Logger QUERY_LOGGER = LoggerFactory
|
||||||
|
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
|
||||||
|
|
||||||
|
protected final ElasticsearchConverter converter;
|
||||||
|
protected final SimpleElasticsearchMappingContext mappingContext;
|
||||||
|
protected final EntityOperations entityOperations;
|
||||||
|
|
||||||
|
protected @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||||
|
protected RoutingResolver routingResolver;
|
||||||
|
|
||||||
|
protected @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||||
|
|
||||||
|
// region Initialization
|
||||||
|
protected AbstractReactiveElasticsearchTemplate(@Nullable ElasticsearchConverter converter) {
|
||||||
|
|
||||||
|
this.converter = converter != null ? converter : createElasticsearchConverter();
|
||||||
|
this.mappingContext = (SimpleElasticsearchMappingContext) this.converter.getMappingContext();
|
||||||
|
this.entityOperations = new EntityOperations(this.mappingContext);
|
||||||
|
this.routingResolver = new DefaultRoutingResolver(this.mappingContext);
|
||||||
|
|
||||||
|
// initialize the VersionInfo class in the initialization phase
|
||||||
|
// noinspection ResultOfMethodCallIgnored
|
||||||
|
VersionInfo.versionProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElasticsearchConverter getElasticsearchConverter() {
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return copy of this instance.
|
||||||
|
*/
|
||||||
|
private AbstractReactiveElasticsearchTemplate copy() {
|
||||||
|
|
||||||
|
AbstractReactiveElasticsearchTemplate copy = doCopy();
|
||||||
|
copy.setRefreshPolicy(refreshPolicy);
|
||||||
|
|
||||||
|
if (entityCallbacks != null) {
|
||||||
|
copy.setEntityCallbacks(entityCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.setRoutingResolver(routingResolver);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected AbstractReactiveElasticsearchTemplate doCopy();
|
||||||
|
|
||||||
|
private ElasticsearchConverter createElasticsearchConverter() {
|
||||||
|
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(
|
||||||
|
new SimpleElasticsearchMappingContext());
|
||||||
|
mappingElasticsearchConverter.afterPropertiesSet();
|
||||||
|
return mappingElasticsearchConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 {@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logs the versions of the different Elasticsearch components.
|
||||||
|
*
|
||||||
|
* @return a Mono signalling finished execution
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public Mono<Void> logVersions() {
|
||||||
|
|
||||||
|
return getVendor() //
|
||||||
|
.zipWith(getRuntimeLibraryVersion()) //
|
||||||
|
.zipWith(getClusterVersion()) //
|
||||||
|
.doOnNext(objects -> {
|
||||||
|
VersionInfo.logVersions(objects.getT1().getT1(), objects.getT1().getT2(), objects.getT2());
|
||||||
|
}).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region routing
|
||||||
|
private void setRoutingResolver(RoutingResolver routingResolver) {
|
||||||
|
|
||||||
|
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||||
|
|
||||||
|
this.routingResolver = routingResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver) {
|
||||||
|
|
||||||
|
Assert.notNull(routingResolver, "routingResolver must not be null");
|
||||||
|
|
||||||
|
AbstractReactiveElasticsearchTemplate copy = copy();
|
||||||
|
copy.setRoutingResolver(routingResolver);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region DocumentOperations
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> save(T entity) {
|
||||||
|
return save(entity, getIndexCoordinatesFor(entity.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz) {
|
||||||
|
return saveAll(entities, getIndexCoordinatesFor(clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IndexQuery getIndexQuery(Object value) {
|
||||||
|
EntityOperations.AdaptableEntity<?> entity = entityOperations.forEntity(value, converter.getConversionService(),
|
||||||
|
routingResolver);
|
||||||
|
|
||||||
|
Object id = entity.getId();
|
||||||
|
IndexQuery query = new IndexQuery();
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
query.setId(id.toString());
|
||||||
|
}
|
||||||
|
query.setObject(value);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query.setRouting(entity.getRouting());
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
|
||||||
|
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
|
||||||
|
.getPersistentEntity(entity.getClass());
|
||||||
|
|
||||||
|
if (persistentEntity != null) {
|
||||||
|
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||||
|
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||||
|
|
||||||
|
// Only deal with text because ES generated Ids are strings!
|
||||||
|
if (indexedObjectInformation.getId() != null && idProperty != null
|
||||||
|
&& idProperty.getType().isAssignableFrom(String.class)) {
|
||||||
|
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
|
||||||
|
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
|
||||||
|
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
|
||||||
|
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
|
||||||
|
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection unchecked
|
||||||
|
T updatedEntity = (T) propertyAccessor.getBean();
|
||||||
|
return updatedEntity;
|
||||||
|
} else {
|
||||||
|
EntityOperations.AdaptableEntity<T> adaptableEntity = entityOperations.forEntity(entity,
|
||||||
|
converter.getConversionService(), routingResolver);
|
||||||
|
adaptableEntity.populateIdIfNecessary(indexedObjectInformation.getId());
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz) {
|
||||||
|
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> exists(String id, Class<?> entityType) {
|
||||||
|
return doExists(id, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> exists(String id, IndexCoordinates index) {
|
||||||
|
return doExists(id, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> save(T entity, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(entity, "Entity must not be null!");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
return maybeCallBeforeConvert(entity, index)
|
||||||
|
.flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) //
|
||||||
|
.map(it -> {
|
||||||
|
T savedEntity = it.getT1();
|
||||||
|
IndexResponseMetaData indexResponseMetaData = it.getT2();
|
||||||
|
return updateIndexedObject(savedEntity, IndexedObjectInformation.of( //
|
||||||
|
indexResponseMetaData.getId(), //
|
||||||
|
indexResponseMetaData.getSeqNo(), //
|
||||||
|
indexResponseMetaData.getPrimaryTerm(), //
|
||||||
|
indexResponseMetaData.getVersion()));
|
||||||
|
}).flatMap(saved -> maybeCallAfterSave(saved, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoordinates index);
|
||||||
|
|
||||||
|
abstract protected Mono<Boolean> doExists(String id, IndexCoordinates index);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<T> get(String id, Class<T> entityType) {
|
||||||
|
return get(id, entityType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> delete(Object entity, IndexCoordinates index) {
|
||||||
|
|
||||||
|
EntityOperations.AdaptableEntity<?> elasticsearchEntity = entityOperations.forEntity(entity,
|
||||||
|
converter.getConversionService(), routingResolver);
|
||||||
|
|
||||||
|
if (elasticsearchEntity.getId() == null) {
|
||||||
|
return Mono.error(new IllegalArgumentException("entity must have an id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mono.defer(() -> {
|
||||||
|
String id = converter.convertId(elasticsearchEntity.getId());
|
||||||
|
String routing = elasticsearchEntity.getRouting();
|
||||||
|
return doDeleteById(id, routing, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> delete(Object entity) {
|
||||||
|
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, routingResolver.getRouting(), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
|
||||||
|
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region SearchDocument
|
||||||
|
@Override
|
||||||
|
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
|
||||||
|
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||||
|
return doFind(query, entityType, index).concatMap(callback::toSearchHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> returnType) {
|
||||||
|
return search(query, entityType, returnType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType) {
|
||||||
|
return searchForPage(query, entityType, resultType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType,
|
||||||
|
IndexCoordinates index) {
|
||||||
|
|
||||||
|
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||||
|
|
||||||
|
return doFindForResponse(query, entityType, index) //
|
||||||
|
.flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) //
|
||||||
|
.flatMap(callback::toEntity) //
|
||||||
|
.collectList() //
|
||||||
|
.map(entities -> SearchHitMapping.mappingFor(resultType, converter) //
|
||||||
|
.mapHits(searchDocumentResponse, entities))) //
|
||||||
|
.map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType) {
|
||||||
|
return searchForHits(query, entityType, resultType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType,
|
||||||
|
IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(entityType, "entityType must not be null");
|
||||||
|
Assert.notNull(resultType, "resultType must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
||||||
|
|
||||||
|
return doFindForResponse(query, entityType, index) //
|
||||||
|
.flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) //
|
||||||
|
.flatMap(callback::toEntity) //
|
||||||
|
.collectList() //
|
||||||
|
.map(entities -> SearchHitMapping.mappingFor(resultType, converter) //
|
||||||
|
.mapHits(searchDocumentResponse, entities))) //
|
||||||
|
.map(ReactiveSearchHitSupport::searchHitsFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index);
|
||||||
|
|
||||||
|
abstract protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz,
|
||||||
|
IndexCoordinates index);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
|
||||||
|
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Suggest> suggest(Query query, Class<?> entityType) {
|
||||||
|
return suggest(query, entityType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(query, "query must not be null");
|
||||||
|
Assert.notNull(entityType, "entityType must not be null");
|
||||||
|
Assert.notNull(index, "index must not be null");
|
||||||
|
|
||||||
|
return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> {
|
||||||
|
Suggest suggest = searchDocumentResponse.getSuggest();
|
||||||
|
SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest);
|
||||||
|
return suggest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Long> count(Query query, Class<?> entityType) {
|
||||||
|
return count(query, entityType, getIndexCoordinatesFor(entityType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Long> count(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
return doCount(query, entityType, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index);
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> Mono<Document> maybeCallbackAfterLoad(Document document, Class<T> type, IndexCoordinates index) {
|
||||||
|
if (entityCallbacks != null) {
|
||||||
|
return entityCallbacks.callback(ReactiveAfterLoadCallback.class, document, type, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mono.just(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected interface DocumentCallback<T> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Mono<T> toEntity(@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> toEntity(@Nullable Document document) {
|
||||||
|
if (document == null) {
|
||||||
|
return Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return maybeCallbackAfterLoad(document, type, index) //
|
||||||
|
.flatMap(documentAfterLoad -> {
|
||||||
|
|
||||||
|
T entity = reader.read(type, documentAfterLoad);
|
||||||
|
|
||||||
|
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
|
||||||
|
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
|
||||||
|
documentAfterLoad.getSeqNo(), //
|
||||||
|
documentAfterLoad.getPrimaryTerm(), //
|
||||||
|
documentAfterLoad.getVersion()); //
|
||||||
|
entity = updateIndexedObject(entity, indexedObjectInformation);
|
||||||
|
|
||||||
|
return maybeCallAfterConvert(entity, documentAfterLoad, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected interface SearchDocumentCallback<T> {
|
||||||
|
|
||||||
|
Mono<T> toEntity(SearchDocument response);
|
||||||
|
|
||||||
|
Mono<SearchHit<T>> toSearchHit(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<T> toEntity(SearchDocument response) {
|
||||||
|
return delegate.toEntity(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<SearchHit<T>> toSearchHit(SearchDocument response) {
|
||||||
|
return toEntity(response).map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Helper methods
|
||||||
|
@Override
|
||||||
|
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
||||||
|
|
||||||
|
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntityFor(clazz);
|
||||||
|
|
||||||
|
Assert.notNull(persistentEntity, "could not get indexCoordinates for class " + clazz.getName());
|
||||||
|
|
||||||
|
return persistentEntity.getIndexCoordinates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public ElasticsearchPersistentEntity<?> getPersistentEntityFor(@Nullable Class<?> type) {
|
||||||
|
return type != null ? mappingContext.getPersistentEntity(type) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the vendor name of the used cluster and client library
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
abstract protected Mono<String> getVendor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the version of the used client runtime library.
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
abstract protected Mono<String> getRuntimeLibraryVersion();
|
||||||
|
|
||||||
|
abstract protected Mono<String> getClusterVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value class to capture client independent information from a response to an index request.
|
||||||
|
*/
|
||||||
|
public static class IndexResponseMetaData {
|
||||||
|
private final String id;
|
||||||
|
private final long seqNo;
|
||||||
|
private final long primaryTerm;
|
||||||
|
private final long version;
|
||||||
|
|
||||||
|
public IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) {
|
||||||
|
this.id = id;
|
||||||
|
this.seqNo = seqNo;
|
||||||
|
this.primaryTerm = primaryTerm;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSeqNo() {
|
||||||
|
return seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPrimaryTerm() {
|
||||||
|
return primaryTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
protected class Entities<T> {
|
||||||
|
private final List<T> entities;
|
||||||
|
|
||||||
|
public Entities(List<T> entities) {
|
||||||
|
|
||||||
|
Assert.notNull(entities, "entities cannot be null");
|
||||||
|
|
||||||
|
this.entities = entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return entities.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IndexQuery> indexQueries() {
|
||||||
|
return entities.stream().map(value -> getIndexQuery(value)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public T entityAt(long index) {
|
||||||
|
// it's safe to cast to int because the original indexed collection was fitting in memory
|
||||||
|
int intIndex = (int) index;
|
||||||
|
return entities.get(intIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -44,7 +44,6 @@ import org.springframework.util.Assert;
|
|||||||
* @author Rasmus Faber-Espensen
|
* @author Rasmus Faber-Espensen
|
||||||
* @author James Bodkin
|
* @author James Bodkin
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.4
|
|
||||||
*/
|
*/
|
||||||
class CriteriaQueryProcessor {
|
class CriteriaQueryProcessor {
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core;
|
package org.springframework.data.elasticsearch.core;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -21,7 +21,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
|||||||
import org.springframework.data.elasticsearch.core.cluster.ElasticsearchClusterOperations;
|
import org.springframework.data.elasticsearch.core.cluster.ElasticsearchClusterOperations;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponseBuilder;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
@ -118,13 +117,16 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
|
|
||||||
private final RestHighLevelClient client;
|
private final RestHighLevelClient client;
|
||||||
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
|
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||||
|
protected RequestFactory requestFactory;
|
||||||
|
|
||||||
// region Initialization
|
// region _initialization
|
||||||
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
||||||
|
|
||||||
Assert.notNull(client, "Client must not be null!");
|
Assert.notNull(client, "Client must not be null!");
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||||
@ -134,12 +136,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
Assert.notNull(client, "Client must not be null!");
|
Assert.notNull(client, "Client must not be null!");
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AbstractElasticsearchTemplate doCopy() {
|
protected AbstractElasticsearchTemplate doCopy() {
|
||||||
return new ElasticsearchRestTemplate(client, elasticsearchConverter);
|
ElasticsearchRestTemplate copy = new ElasticsearchRestTemplate(client, elasticsearchConverter);
|
||||||
|
copy.requestFactory = this.requestFactory;
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public RequestFactory getRequestFactory() {
|
||||||
|
return requestFactory;
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region IndexOperations
|
// region IndexOperations
|
||||||
@ -282,22 +295,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReindexResponse reindex(ReindexRequest postReindexRequest) {
|
public ReindexResponse reindex(ReindexRequest reindexRequest) {
|
||||||
|
|
||||||
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
|
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||||
|
|
||||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
|
org.elasticsearch.index.reindex.ReindexRequest reindexRequestES = requestFactory.reindexRequest(reindexRequest);
|
||||||
BulkByScrollResponse bulkByScrollResponse = execute(
|
BulkByScrollResponse bulkByScrollResponse = execute(
|
||||||
client -> client.reindex(reindexRequest, RequestOptions.DEFAULT));
|
client -> client.reindex(reindexRequestES, RequestOptions.DEFAULT));
|
||||||
return ResponseConverter.reindexResponseOf(bulkByScrollResponse);
|
return ResponseConverter.reindexResponseOf(bulkByScrollResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String submitReindex(ReindexRequest postReindexRequest) {
|
public String submitReindex(ReindexRequest reindexRequest) {
|
||||||
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
|
|
||||||
|
|
||||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
|
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||||
return execute(client -> client.submitReindexTask(reindexRequest, RequestOptions.DEFAULT).getTask());
|
|
||||||
|
org.elasticsearch.index.reindex.ReindexRequest reindexRequestES = requestFactory.reindexRequest(reindexRequest);
|
||||||
|
return execute(client -> client.submitReindexTask(reindexRequestES, RequestOptions.DEFAULT).getTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||||
@ -387,7 +401,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||||
|
|
||||||
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
|
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||||
@ -411,12 +425,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||||
index);
|
index);
|
||||||
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
|
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
public <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
|
||||||
Class<T> clazz, IndexCoordinates index) {
|
IndexCoordinates index) {
|
||||||
|
|
||||||
SearchScrollRequest request = new SearchScrollRequest(scrollId);
|
SearchScrollRequest request = new SearchScrollRequest(scrollId);
|
||||||
request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
||||||
@ -426,7 +440,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||||
index);
|
index);
|
||||||
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
|
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -459,7 +473,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||||
List<SearchHits<T>> res = new ArrayList<>(queries.size());
|
List<SearchHits<T>> res = new ArrayList<>(queries.size());
|
||||||
for (int i = 0; i < queries.size(); i++) {
|
for (int i = 0; i < queries.size(); i++) {
|
||||||
res.add(callback.doWith(SearchDocumentResponse.from(items[i].getResponse(), getEntityCreator(documentCallback))));
|
res.add(callback
|
||||||
|
.doWith(SearchDocumentResponseBuilder.from(items[i].getResponse(), getEntityCreator(documentCallback))));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -492,7 +507,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
index);
|
index);
|
||||||
|
|
||||||
SearchResponse response = items[i].getResponse();
|
SearchResponse response = items[i].getResponse();
|
||||||
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
|
res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -525,7 +540,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
index);
|
index);
|
||||||
|
|
||||||
SearchResponse response = items[i].getResponse();
|
SearchResponse response = items[i].getResponse();
|
||||||
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
|
res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))));
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -537,11 +552,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
|
|
||||||
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region ClientCallback
|
// region ClientCallback
|
||||||
/**
|
/**
|
||||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||||
|
@ -73,15 +73,15 @@ public class EntityOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link AdaptibleEntity} for the given bean and {@link ConversionService} and {@link RoutingResolver}.
|
* Creates a new {@link AdaptableEntity} for the given bean and {@link ConversionService} and {@link RoutingResolver}.
|
||||||
*
|
*
|
||||||
* @param entity must not be {@literal null}.
|
* @param entity must not be {@literal null}.
|
||||||
* @param conversionService must not be {@literal null}.
|
* @param conversionService must not be {@literal null}.
|
||||||
* @param routingResolver the {@link RoutingResolver}, must not be {@literal null}
|
* @param routingResolver the {@link RoutingResolver}, must not be {@literal null}
|
||||||
* @return the {@link AdaptibleEntity}
|
* @return the {@link AdaptableEntity}
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService,
|
public <T> AdaptableEntity<T> forEntity(T entity, ConversionService conversionService,
|
||||||
RoutingResolver routingResolver) {
|
RoutingResolver routingResolver) {
|
||||||
|
|
||||||
Assert.notNull(entity, "Bean must not be null!");
|
Assert.notNull(entity, "Bean must not be null!");
|
||||||
@ -91,7 +91,7 @@ public class EntityOperations {
|
|||||||
return new SimpleMappedEntity((Map<String, Object>) entity);
|
return new SimpleMappedEntity((Map<String, Object>) entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver);
|
return AdaptableMappedEntity.of(entity, context, conversionService, routingResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +199,7 @@ public class EntityOperations {
|
|||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
public interface AdaptibleEntity<T> extends Entity<T> {
|
public interface AdaptableEntity<T> extends Entity<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
|
||||||
@ -267,7 +267,7 @@ public class EntityOperations {
|
|||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
private static class MapBackedEntity<T extends Map<String, Object>> implements AdaptibleEntity<T> {
|
private static class MapBackedEntity<T extends Map<String, Object>> implements AdaptableEntity<T> {
|
||||||
|
|
||||||
public MapBackedEntity(T map) {
|
public MapBackedEntity(T map) {
|
||||||
|
|
||||||
@ -289,7 +289,7 @@ public class EntityOperations {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
|
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#populateIdIfNecessary(java.lang.Object)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@ -302,7 +302,7 @@ public class EntityOperations {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#initializeVersionProperty()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public T initializeVersionProperty() {
|
public T initializeVersionProperty() {
|
||||||
@ -311,7 +311,7 @@ public class EntityOperations {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getVersion()
|
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#getVersion()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -331,7 +331,7 @@ public class EntityOperations {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#incrementVersion()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public T incrementVersion() {
|
public T incrementVersion() {
|
||||||
@ -476,7 +476,7 @@ public class EntityOperations {
|
|||||||
* @param <T>
|
* @param <T>
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
|
private static class AdaptableMappedEntity<T> extends MappedEntity<T> implements AdaptableEntity<T> {
|
||||||
|
|
||||||
private final ElasticsearchPersistentEntity<?> entity;
|
private final ElasticsearchPersistentEntity<?> entity;
|
||||||
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
private final ConvertingPropertyAccessor<T> propertyAccessor;
|
||||||
@ -484,7 +484,7 @@ public class EntityOperations {
|
|||||||
private final ConversionService conversionService;
|
private final ConversionService conversionService;
|
||||||
private final RoutingResolver routingResolver;
|
private final RoutingResolver routingResolver;
|
||||||
|
|
||||||
private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity<?> entity,
|
private AdaptableMappedEntity(T bean, ElasticsearchPersistentEntity<?> entity,
|
||||||
IdentifierAccessor identifierAccessor, ConvertingPropertyAccessor<T> propertyAccessor,
|
IdentifierAccessor identifierAccessor, ConvertingPropertyAccessor<T> propertyAccessor,
|
||||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||||
|
|
||||||
@ -497,7 +497,7 @@ public class EntityOperations {
|
|||||||
this.routingResolver = routingResolver;
|
this.routingResolver = routingResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T> AdaptibleEntity<T> of(T bean,
|
static <T> AdaptableEntity<T> of(T bean,
|
||||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context,
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context,
|
||||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||||
|
|
||||||
@ -505,7 +505,7 @@ public class EntityOperations {
|
|||||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(bean);
|
||||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(bean);
|
||||||
|
|
||||||
return new AdaptibleMappedEntity<>(bean, entity, identifierAccessor,
|
return new AdaptableMappedEntity<>(bean, entity, identifierAccessor,
|
||||||
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver);
|
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,8 +228,7 @@ public interface IndexOperations {
|
|||||||
boolean putTemplate(PutTemplateRequest putTemplateRequest);
|
boolean putTemplate(PutTemplateRequest putTemplateRequest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets an index template using the legacy Elasticsearch
|
* gets an index template using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param templateName the template name
|
* @param templateName the template name
|
||||||
* @return TemplateData, {@literal null} if no template with the given name exists.
|
* @return TemplateData, {@literal null} if no template with the given name exists.
|
||||||
@ -241,8 +240,7 @@ public interface IndexOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets an index template using the legacy Elasticsearch
|
* gets an index template using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param getTemplateRequest the request parameters
|
* @param getTemplateRequest the request parameters
|
||||||
* @return TemplateData, {@literal null} if no template with the given name exists.
|
* @return TemplateData, {@literal null} if no template with the given name exists.
|
||||||
@ -252,8 +250,7 @@ public interface IndexOperations {
|
|||||||
TemplateData getTemplate(GetTemplateRequest getTemplateRequest);
|
TemplateData getTemplate(GetTemplateRequest getTemplateRequest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if an index template exists using the legacy Elasticsearch
|
* check if an index template exists using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param templateName the template name
|
* @param templateName the template name
|
||||||
* @return {@literal true} if the index exists
|
* @return {@literal true} if the index exists
|
||||||
@ -264,8 +261,7 @@ public interface IndexOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check if an index template exists using the legacy Elasticsearch
|
* check if an index template exists using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param existsTemplateRequest the request parameters
|
* @param existsTemplateRequest the request parameters
|
||||||
* @return {@literal true} if the index exists
|
* @return {@literal true} if the index exists
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core;
|
package org.springframework.data.elasticsearch.core;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response object for items returned from multiget requests, encapsulating the returned data and potential error
|
* Response object for items returned from multiget requests, encapsulating the returned data and potential error
|
||||||
* information.
|
* information.
|
||||||
*
|
*
|
||||||
* @param <T> the entity type
|
* @param <T> the entity type
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
@ -61,16 +62,20 @@ public class MultiGetItem<T> {
|
|||||||
@Nullable private final String type;
|
@Nullable private final String type;
|
||||||
@Nullable private final String id;
|
@Nullable private final String id;
|
||||||
@Nullable private final Exception exception;
|
@Nullable private final Exception exception;
|
||||||
|
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception exception) {
|
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception exception,
|
||||||
|
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Failure of(String index, String type, String id, Exception exception) {
|
public static Failure of(String index, @Nullable String type, String id, @Nullable Exception exception,
|
||||||
return new Failure(index, type, id, exception);
|
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
|
return new Failure(index, type, id, exception, elasticsearchErrorCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -92,5 +97,10 @@ public class MultiGetItem<T> {
|
|||||||
public Exception getException() {
|
public Exception getException() {
|
||||||
return exception;
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ElasticsearchErrorCause getElasticsearchErrorCause() {
|
||||||
|
return elasticsearchErrorCause;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,9 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
|||||||
* @param callback must not be {@literal null}.
|
* @param callback must not be {@literal null}.
|
||||||
* @param <T> the type the Publisher emits
|
* @param <T> the type the Publisher emits
|
||||||
* @return the {@link Publisher} emitting results.
|
* @return the {@link Publisher} emitting results.
|
||||||
|
* @deprecated since 4.4, use the execute methods from the implementing classes (they are client specific)
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
|
<T> Publisher<T> execute(ClientCallback<Publisher<T>> callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +128,7 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
|||||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||||
* {@link ReactiveElasticsearchClient}.
|
* {@link ReactiveElasticsearchClient}.
|
||||||
*
|
*
|
||||||
* @param <T>
|
* @param <T> result type of the callback
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
|
@ -23,10 +23,7 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.DocWriteResponse;
|
import org.elasticsearch.action.DocWriteResponse;
|
||||||
import org.elasticsearch.action.bulk.BulkItemResponse;
|
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||||
@ -48,49 +45,27 @@ import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
|||||||
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
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.BulkFailureException;
|
||||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||||
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
|
|
||||||
import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations;
|
import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations;
|
||||||
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
|
||||||
import org.springframework.data.elasticsearch.core.document.Document;
|
|
||||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
|
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponseBuilder;
|
||||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterLoadCallback;
|
|
||||||
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;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
|
||||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
|
||||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||||
import org.springframework.data.elasticsearch.core.query.Query;
|
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.core.query.UpdateQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||||
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
|
|
||||||
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
|
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
|
||||||
import org.springframework.data.elasticsearch.support.VersionInfo;
|
|
||||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
|
||||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -109,98 +84,37 @@ import org.springframework.util.Assert;
|
|||||||
* @author Sijia Liu
|
* @author Sijia Liu
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
|
||||||
|
|
||||||
private static final Log QUERY_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.core.QUERY");
|
|
||||||
|
|
||||||
private final ReactiveElasticsearchClient client;
|
private final ReactiveElasticsearchClient client;
|
||||||
private final ElasticsearchConverter converter;
|
|
||||||
private final SimpleElasticsearchMappingContext mappingContext;
|
|
||||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||||
private final EntityOperations operations;
|
|
||||||
protected RequestFactory requestFactory;
|
protected RequestFactory requestFactory;
|
||||||
|
|
||||||
private @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
|
||||||
private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled();
|
private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled();
|
||||||
|
|
||||||
private @Nullable ReactiveEntityCallbacks entityCallbacks;
|
|
||||||
|
|
||||||
private RoutingResolver routingResolver;
|
|
||||||
|
|
||||||
// region Initialization
|
// region Initialization
|
||||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) {
|
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) {
|
||||||
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
|
this(client, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) {
|
public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, @Nullable ElasticsearchConverter converter) {
|
||||||
|
|
||||||
|
super(converter);
|
||||||
|
|
||||||
Assert.notNull(client, "client must not be null");
|
Assert.notNull(client, "client must not be null");
|
||||||
Assert.notNull(converter, "converter must not be null");
|
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.converter = converter;
|
|
||||||
|
|
||||||
this.mappingContext = (SimpleElasticsearchMappingContext) converter.getMappingContext();
|
|
||||||
this.routingResolver = new DefaultRoutingResolver(this.mappingContext);
|
|
||||||
|
|
||||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||||
this.operations = new EntityOperations(this.mappingContext);
|
this.requestFactory = new RequestFactory(this.converter);
|
||||||
this.requestFactory = new RequestFactory(converter);
|
|
||||||
|
|
||||||
// initialize the VersionInfo class in the initialization phase
|
|
||||||
// noinspection ResultOfMethodCallIgnored
|
|
||||||
VersionInfo.versionProperties();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactiveElasticsearchTemplate copy() {
|
protected ReactiveElasticsearchTemplate doCopy() {
|
||||||
|
|
||||||
ReactiveElasticsearchTemplate copy = new ReactiveElasticsearchTemplate(client, converter);
|
ReactiveElasticsearchTemplate copy = new ReactiveElasticsearchTemplate(client, converter);
|
||||||
copy.setRefreshPolicy(refreshPolicy);
|
|
||||||
copy.setIndicesOptions(indicesOptions);
|
copy.setIndicesOptions(indicesOptions);
|
||||||
copy.setEntityCallbacks(entityCallbacks);
|
|
||||||
copy.setRoutingResolver(routingResolver);
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* logs the versions of the different Elasticsearch components.
|
|
||||||
*
|
|
||||||
* @return a Mono signalling finished execution
|
|
||||||
* @since 4.3
|
|
||||||
*/
|
|
||||||
public Mono<Void> logVersions() {
|
|
||||||
return getVendor() //
|
|
||||||
.doOnNext(vendor -> getRuntimeLibraryVersion() //
|
|
||||||
.doOnNext(runtimeLibraryVersion -> getClusterVersion() //
|
|
||||||
.doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) //
|
|
||||||
.then(); //
|
|
||||||
}
|
|
||||||
|
|
||||||
@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}.
|
* Set the default {@link IndicesOptions} for {@link SearchRequest search requests}.
|
||||||
*
|
*
|
||||||
@ -210,58 +124,14 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
this.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
|
// endregion
|
||||||
|
|
||||||
// region DocumentOperations
|
// region DocumentOperations
|
||||||
/*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see org.springframework.data.elasticsearch.core.ReactiveDElasticsearchOperations#index(Object, IndexCoordinates)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public <T> Mono<T> save(T entity, IndexCoordinates index) {
|
|
||||||
|
|
||||||
Assert.notNull(entity, "Entity must not be null!");
|
|
||||||
|
|
||||||
return maybeCallBeforeConvert(entity, index)
|
|
||||||
.flatMap(entityAfterBeforeConversionCallback -> doIndex(entityAfterBeforeConversionCallback, index)) //
|
|
||||||
.map(it -> {
|
|
||||||
T savedEntity = it.getT1();
|
|
||||||
IndexResponse indexResponse = it.getT2();
|
|
||||||
return updateIndexedObject(savedEntity, IndexedObjectInformation.of(indexResponse.getId(),
|
|
||||||
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
|
|
||||||
}).flatMap(saved -> maybeCallAfterSave(saved, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Mono<T> save(T entity) {
|
|
||||||
return save(entity, getIndexCoordinatesFor(entity.getClass()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entities, Class<T> clazz) {
|
|
||||||
return saveAll(entities, getIndexCoordinatesFor(clazz));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
|
public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPublisher, IndexCoordinates index) {
|
||||||
|
|
||||||
Assert.notNull(entitiesPublisher, "Entities must not be null!");
|
Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!");
|
||||||
|
|
||||||
return entitiesPublisher //
|
return entitiesPublisher //
|
||||||
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||||
@ -275,7 +145,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
}
|
}
|
||||||
|
|
||||||
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
||||||
.index().flatMap(indexAndResponse -> {
|
.index() //
|
||||||
|
.flatMap(indexAndResponse -> {
|
||||||
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||||
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
|
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
|
||||||
|
|
||||||
@ -288,51 +159,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
|
|
||||||
|
|
||||||
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
|
|
||||||
.getPersistentEntity(entity.getClass());
|
|
||||||
|
|
||||||
if (persistentEntity != null) {
|
|
||||||
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
|
||||||
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
|
|
||||||
|
|
||||||
// Only deal with text because ES generated Ids are strings!
|
|
||||||
if (indexedObjectInformation.getId() != null && idProperty != null
|
|
||||||
&& idProperty.getType().isAssignableFrom(String.class)) {
|
|
||||||
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
|
|
||||||
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
|
|
||||||
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
|
|
||||||
// noinspection ConstantConditions
|
|
||||||
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
|
|
||||||
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
|
|
||||||
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
|
||||||
// noinspection ConstantConditions
|
|
||||||
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
// noinspection unchecked
|
|
||||||
T updatedEntity = (T) propertyAccessor.getBean();
|
|
||||||
return updatedEntity;
|
|
||||||
} else {
|
|
||||||
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
|
|
||||||
routingResolver);
|
|
||||||
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
|
|
||||||
}
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz) {
|
|
||||||
return multiGet(query, clazz, getIndexCoordinatesFor(clazz));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
|
public <T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
@ -352,16 +178,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
|
||||||
|
|
||||||
Assert.notNull(queries, "List of UpdateQuery must not be null");
|
|
||||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
|
||||||
Assert.notNull(index, "Index must not be null");
|
|
||||||
|
|
||||||
return doBulkOperation(queries, bulkOptions, index).then();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customization hook on the actual execution result {@link Publisher}. <br />
|
* Customization hook on the actual execution result {@link Publisher}. <br />
|
||||||
* You know what you're doing here? Well fair enough, go ahead on your own risk.
|
* You know what you're doing here? Well fair enough, go ahead on your own risk.
|
||||||
@ -373,7 +189,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
return Mono.from(execute(client -> client.index(request)));
|
return Mono.from(execute(client -> client.index(request)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||||
|
|
||||||
|
Assert.notNull(queries, "List of UpdateQuery must not be null");
|
||||||
|
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||||
|
Assert.notNull(index, "Index must not be null");
|
||||||
|
|
||||||
|
return doBulkOperation(queries, bulkOptions, index).then();
|
||||||
|
}
|
||||||
|
|
||||||
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||||
|
|
||||||
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
||||||
return client.bulk(bulkRequest) //
|
return client.bulk(bulkRequest) //
|
||||||
.onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) //
|
.onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) //
|
||||||
@ -401,17 +228,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||||
public Mono<Boolean> exists(String id, Class<?> entityType) {
|
|
||||||
return doExists(id, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Boolean> exists(String id, IndexCoordinates index) {
|
|
||||||
return doExists(id, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
|
||||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
return Mono.defer(() -> doExists(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,54 +244,17 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
.onErrorReturn(NoSuchIndexException.class, false);
|
.onErrorReturn(NoSuchIndexException.class, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> Mono<Tuple2<T, IndexResponse>> doIndex(T entity, IndexCoordinates index) {
|
protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoordinates index) {
|
||||||
|
|
||||||
IndexRequest request = requestFactory.indexRequest(getIndexQuery(entity), index);
|
IndexRequest request = requestFactory.indexRequest(getIndexQuery(entity), index);
|
||||||
request = prepareIndexRequest(entity, request);
|
request = prepareIndexRequest(entity, request);
|
||||||
return Mono.just(entity).zipWith(doIndex(request));
|
return Mono.just(entity).zipWith(doIndex(request) //
|
||||||
}
|
.map(indexResponse -> new IndexResponseMetaData( //
|
||||||
|
indexResponse.getId(), //
|
||||||
private IndexQuery getIndexQuery(Object value) {
|
indexResponse.getSeqNo(), //
|
||||||
AdaptibleEntity<?> entity = operations.forEntity(value, converter.getConversionService(), routingResolver);
|
indexResponse.getPrimaryTerm(), //
|
||||||
|
indexResponse.getVersion() //
|
||||||
Object id = entity.getId();
|
))); //
|
||||||
IndexQuery query = new IndexQuery();
|
|
||||||
|
|
||||||
if (id != null) {
|
|
||||||
query.setId(id.toString());
|
|
||||||
}
|
|
||||||
query.setObject(value);
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query.setRouting(entity.getRouting());
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Mono<T> get(String id, Class<T> entityType) {
|
|
||||||
return get(id, entityType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -482,13 +262,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
|
|
||||||
Assert.notNull(id, "Id must not be null!");
|
Assert.notNull(id, "Id must not be null!");
|
||||||
|
|
||||||
|
GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index);
|
||||||
|
Mono<GetResult> getResult = doGet(request);
|
||||||
|
|
||||||
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||||
|
return getResult.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
||||||
return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
|
|
||||||
return Mono.defer(() -> doGet(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -503,51 +281,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
||||||
* (non-Javadoc)
|
|
||||||
* @see org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations#delete(Object, String, String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Mono<String> delete(Object entity, IndexCoordinates index) {
|
|
||||||
|
|
||||||
AdaptibleEntity<?> elasticsearchEntity = operations.forEntity(entity, converter.getConversionService(),
|
|
||||||
routingResolver);
|
|
||||||
|
|
||||||
if (elasticsearchEntity.getId() == null) {
|
|
||||||
return Mono.error(new IllegalArgumentException("entity must have an id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mono.defer(() -> {
|
|
||||||
String id = converter.convertId(elasticsearchEntity.getId());
|
|
||||||
String routing = elasticsearchEntity.getRouting();
|
|
||||||
return doDeleteById(id, routing, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<String> delete(Object entity) {
|
|
||||||
return delete(entity, getIndexCoordinatesFor(entity.getClass()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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, routingResolver.getRouting(), index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
|
||||||
|
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
DeleteRequest request = requestFactory.deleteRequest(id, routing, index);
|
DeleteRequest request = requestFactory.deleteRequest(id, routing, index);
|
||||||
@ -633,12 +367,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
|
|
||||||
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
|
||||||
|
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
||||||
@ -747,16 +476,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region SearchOperations
|
// region SearchOperations
|
||||||
@Override
|
|
||||||
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
|
|
||||||
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
|
||||||
return doFind(query, entityType, index).concatMap(callback::toSearchHit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> returnType) {
|
|
||||||
return search(query, entityType, returnType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType) {
|
public <T> Mono<SearchPage<T>> searchForPage(Query query, Class<?> entityType, Class<T> resultType) {
|
||||||
@ -778,32 +497,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
.map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
.map(searchHits -> SearchHitSupport.searchPageFor(searchHits, query.getPageable()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType) {
|
|
||||||
return searchForHits(query, entityType, resultType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> Mono<ReactiveSearchHits<T>> searchForHits(Query query, Class<?> entityType, Class<T> resultType,
|
|
||||||
IndexCoordinates index) {
|
|
||||||
|
|
||||||
Assert.notNull(query, "query must not be null");
|
|
||||||
Assert.notNull(entityType, "entityType must not be null");
|
|
||||||
Assert.notNull(resultType, "resultType must not be null");
|
|
||||||
Assert.notNull(index, "index must not be null");
|
|
||||||
|
|
||||||
SearchDocumentCallback<T> callback = new ReadSearchDocumentCallback<>(resultType, index);
|
|
||||||
|
|
||||||
return doFindForResponse(query, entityType, index) //
|
|
||||||
.flatMap(searchDocumentResponse -> Flux.fromIterable(searchDocumentResponse.getSearchDocuments()) //
|
|
||||||
.flatMap(callback::toEntity) //
|
|
||||||
.collectList() //
|
|
||||||
.map(entities -> SearchHitMapping.mappingFor(resultType, converter) //
|
|
||||||
.mapHits(searchDocumentResponse, entities))) //
|
|
||||||
.map(ReactiveSearchHitSupport::searchHitsFor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
|
||||||
|
|
||||||
return Flux.defer(() -> {
|
return Flux.defer(() -> {
|
||||||
|
|
||||||
@ -819,7 +513,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
protected <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||||
|
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
|
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
|
||||||
@ -834,11 +528,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
|
|
||||||
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
|
|
||||||
@ -869,25 +558,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty()).map(ElasticsearchAggregation::new);
|
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty()).map(ElasticsearchAggregation::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Suggest> suggest(Query query, Class<?> entityType) {
|
|
||||||
return suggest(query, entityType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index) {
|
|
||||||
|
|
||||||
Assert.notNull(query, "query must not be null");
|
|
||||||
Assert.notNull(entityType, "entityType must not be null");
|
|
||||||
Assert.notNull(index, "index must not be null");
|
|
||||||
|
|
||||||
return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> {
|
|
||||||
Suggest suggest = searchDocumentResponse.getSuggest();
|
|
||||||
SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest);
|
|
||||||
return suggest;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
||||||
@ -908,17 +578,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||||
public Mono<Long> count(Query query, Class<?> entityType) {
|
|
||||||
return count(query, entityType, getIndexCoordinatesFor(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Long> count(Query query, Class<?> entityType, IndexCoordinates index) {
|
|
||||||
return doCount(query, entityType, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index) {
|
|
||||||
return Mono.defer(() -> {
|
return Mono.defer(() -> {
|
||||||
|
|
||||||
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
||||||
@ -958,7 +618,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Mono.from(execute(client -> client.searchForResponse(request)))
|
return Mono.from(execute(client -> client.searchForResponse(request)))
|
||||||
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
|
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1014,10 +674,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
return request;
|
return request;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Helper methods
|
// region Helper methods
|
||||||
|
@Override
|
||||||
protected Mono<String> getClusterVersion() {
|
protected Mono<String> getClusterVersion() {
|
||||||
try {
|
try {
|
||||||
return Mono.from(execute(ReactiveElasticsearchClient::info))
|
return Mono.from(execute(ReactiveElasticsearchClient::info))
|
||||||
@ -1030,6 +690,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
* @return the vendor name of the used cluster and client library
|
* @return the vendor name of the used cluster and client library
|
||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected Mono<String> getVendor() {
|
protected Mono<String> getVendor() {
|
||||||
return Mono.just("Elasticsearch");
|
return Mono.just("Elasticsearch");
|
||||||
}
|
}
|
||||||
@ -1038,6 +699,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
* @return the version of the used client runtime library.
|
* @return the version of the used client runtime library.
|
||||||
* @since 4.3
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected Mono<String> getRuntimeLibraryVersion() {
|
protected Mono<String> getRuntimeLibraryVersion() {
|
||||||
return Mono.just(Version.CURRENT.toString());
|
return Mono.just(Version.CURRENT.toString());
|
||||||
}
|
}
|
||||||
@ -1072,11 +734,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
return Flux.defer(() -> callback.doWithClient(getClusterClient())).onErrorMap(this::translateException);
|
return Flux.defer(() -> callback.doWithClient(getClusterClient())).onErrorMap(this::translateException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ElasticsearchConverter getElasticsearchConverter() {
|
|
||||||
return converter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
||||||
return new ReactiveIndexTemplate(this, index);
|
return new ReactiveIndexTemplate(this, index);
|
||||||
@ -1092,17 +749,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
return new DefaultReactiveClusterOperations(this);
|
return new DefaultReactiveClusterOperations(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
|
|
||||||
return getPersistentEntityFor(clazz).getIndexCoordinates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public ElasticsearchPersistentEntity<?> getPersistentEntityFor(@Nullable Class<?> type) {
|
|
||||||
return type != null ? mappingContext.getPersistentEntity(type) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain the {@link ReactiveElasticsearchClient} to operate upon.
|
* Obtain the {@link ReactiveElasticsearchClient} to operate upon.
|
||||||
*
|
*
|
||||||
@ -1157,159 +803,4 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
|||||||
|
|
||||||
return potentiallyTranslatedException != null ? potentiallyTranslatedException : 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T> Mono<Document> maybeCallbackAfterLoad(Document document, Class<T> type,
|
|
||||||
IndexCoordinates indexCoordinates) {
|
|
||||||
|
|
||||||
if (entityCallbacks != null) {
|
|
||||||
return entityCallbacks.callback(ReactiveAfterLoadCallback.class, document, type, indexCoordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mono.just(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region routing
|
|
||||||
private void setRoutingResolver(RoutingResolver routingResolver) {
|
|
||||||
|
|
||||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
|
||||||
|
|
||||||
this.routingResolver = routingResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver) {
|
|
||||||
|
|
||||||
Assert.notNull(routingResolver, "routingResolver must not be null");
|
|
||||||
|
|
||||||
ReactiveElasticsearchTemplate copy = copy();
|
|
||||||
copy.setRoutingResolver(routingResolver);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
protected interface DocumentCallback<T> {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Mono<T> toEntity(@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> toEntity(@Nullable Document document) {
|
|
||||||
if (document == null) {
|
|
||||||
return Mono.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return maybeCallbackAfterLoad(document, type, index) //
|
|
||||||
.flatMap(documentAfterLoad -> {
|
|
||||||
|
|
||||||
T entity = reader.read(type, documentAfterLoad);
|
|
||||||
|
|
||||||
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
|
|
||||||
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
|
|
||||||
documentAfterLoad.getSeqNo(), //
|
|
||||||
documentAfterLoad.getPrimaryTerm(), //
|
|
||||||
documentAfterLoad.getVersion()); //
|
|
||||||
entity = updateIndexedObject(entity, indexedObjectInformation);
|
|
||||||
|
|
||||||
return maybeCallAfterConvert(entity, documentAfterLoad, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected interface SearchDocumentCallback<T> {
|
|
||||||
|
|
||||||
Mono<T> toEntity(SearchDocument response);
|
|
||||||
|
|
||||||
Mono<SearchHit<T>> toSearchHit(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<T> toEntity(SearchDocument response) {
|
|
||||||
return delegate.toEntity(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<SearchHit<T>> toSearchHit(SearchDocument response) {
|
|
||||||
return toEntity(response).map(entity -> SearchHitMapping.mappingFor(type, converter).mapHit(response, entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 collection was fitting in memory
|
|
||||||
int intIndex = (int) index;
|
|
||||||
return entities.get(intIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -230,8 +230,7 @@ public interface ReactiveIndexOperations {
|
|||||||
Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest);
|
Mono<Boolean> putTemplate(PutTemplateRequest putTemplateRequest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets an index template using the legacy Elasticsearch
|
* gets an index template using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param templateName the template name
|
* @param templateName the template name
|
||||||
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
||||||
@ -242,8 +241,7 @@ public interface ReactiveIndexOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets an index template using the legacy Elasticsearch
|
* gets an index template using the legacy Elasticsearch interface.
|
||||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
|
||||||
*
|
*
|
||||||
* @param getTemplateRequest the request parameters
|
* @param getTemplateRequest the request parameters
|
||||||
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
||||||
|
@ -180,13 +180,14 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
|||||||
@Override
|
@Override
|
||||||
public Mono<Document> createMapping(Class<?> clazz) {
|
public Mono<Document> createMapping(Class<?> clazz) {
|
||||||
|
|
||||||
|
// noinspection DuplicatedCode
|
||||||
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||||
|
|
||||||
if (mappingAnnotation != null) {
|
if (mappingAnnotation != null) {
|
||||||
String mappingPath = mappingAnnotation.mappingPath();
|
String mappingPath = mappingAnnotation.mappingPath();
|
||||||
|
|
||||||
if (hasText(mappingPath)) {
|
if (hasText(mappingPath)) {
|
||||||
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
|
return ReactiveResourceUtil.loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +231,7 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
|||||||
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(clazz);
|
ElasticsearchPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(clazz);
|
||||||
String settingPath = persistentEntity.settingPath();
|
String settingPath = persistentEntity.settingPath();
|
||||||
return hasText(settingPath) //
|
return hasText(settingPath) //
|
||||||
? loadDocument(settingPath, "@Setting") //
|
? ReactiveResourceUtil.loadDocument(settingPath, "@Setting") //
|
||||||
.map(Settings::new) //
|
.map(Settings::new) //
|
||||||
: Mono.just(persistentEntity.getDefaultSettings());
|
: Mono.just(persistentEntity.getDefaultSettings());
|
||||||
}
|
}
|
||||||
@ -351,23 +352,6 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
|||||||
return converter.getMappingContext().getRequiredPersistentEntity(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(String.format("path in %s has to be defined. Using default empty Document instead.", annotation));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mono.just(Document.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> checkForBoundClass() {
|
private Class<?> checkForBoundClass() {
|
||||||
if (boundClass == null) {
|
if (boundClass == null) {
|
||||||
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core;
|
package org.springframework.data.elasticsearch.core;
|
||||||
|
|
||||||
|
import static org.springframework.util.StringUtils.*;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@ -22,10 +24,13 @@ import java.io.InputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||||
import org.springframework.data.elasticsearch.ResourceFailureException;
|
import org.springframework.data.elasticsearch.ResourceFailureException;
|
||||||
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +41,8 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public abstract class ReactiveResourceUtil {
|
public abstract class ReactiveResourceUtil {
|
||||||
|
|
||||||
|
private static final Log LOGGER = LogFactory.getLog(ReactiveResourceUtil.class);
|
||||||
|
|
||||||
private static final int BUFFER_SIZE = 8_192;
|
private static final int BUFFER_SIZE = 8_192;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,6 +81,33 @@ public abstract class ReactiveResourceUtil {
|
|||||||
throwable -> Mono.error(new ResourceFailureException("Could not load resource from " + url, throwable)));
|
throwable -> Mono.error(new ResourceFailureException("Could not load resource from " + url, throwable)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* loads a Document initialized with data from a given resource path.
|
||||||
|
*
|
||||||
|
* @param path the path to load data from
|
||||||
|
* @param annotation the annotation that had the resource path defined
|
||||||
|
* @return the parsed document
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public static Mono<Document> loadDocument(String path, String annotation) {
|
||||||
|
|
||||||
|
if (hasText(path)) {
|
||||||
|
return readFileFromClasspath(path).flatMap(s -> {
|
||||||
|
if (hasText(s)) {
|
||||||
|
return Mono.just(Document.parse(s));
|
||||||
|
} else {
|
||||||
|
return Mono.just(Document.create());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (LOGGER.isInfoEnabled()) {
|
||||||
|
LOGGER.info(String.format("path in %s has to be defined. Using default instead.", annotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mono.just(Document.create());
|
||||||
|
}
|
||||||
|
|
||||||
// Utility constructor
|
// Utility constructor
|
||||||
private ReactiveResourceUtil() {}
|
private ReactiveResourceUtil() {}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,15 @@ import static org.springframework.util.CollectionUtils.*;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.elasticsearch.action.DocWriteRequest;
|
import org.elasticsearch.action.DocWriteRequest;
|
||||||
@ -41,7 +49,6 @@ import org.elasticsearch.action.get.GetRequest;
|
|||||||
import org.elasticsearch.action.get.MultiGetRequest;
|
import org.elasticsearch.action.get.MultiGetRequest;
|
||||||
import org.elasticsearch.action.index.IndexRequest;
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
import org.elasticsearch.action.search.SearchRequest;
|
import org.elasticsearch.action.search.SearchRequest;
|
||||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
|
||||||
import org.elasticsearch.action.search.SearchType;
|
import org.elasticsearch.action.search.SearchType;
|
||||||
import org.elasticsearch.action.support.ActiveShardCount;
|
import org.elasticsearch.action.support.ActiveShardCount;
|
||||||
import org.elasticsearch.action.support.WriteRequest;
|
import org.elasticsearch.action.support.WriteRequest;
|
||||||
@ -270,7 +277,7 @@ class RequestFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GetIndexRequest getIndexRequest(IndexCoordinates index) {
|
public GetIndexRequest getIndexRequest(IndexCoordinates index) {
|
||||||
return new GetIndexRequest(index.getIndexNames());
|
return new GetIndexRequest(index.getIndexNames()).humanReadable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndicesExistsRequest indicesExistsRequest(IndexCoordinates index) {
|
public IndicesExistsRequest indicesExistsRequest(IndexCoordinates index) {
|
||||||
@ -405,7 +412,7 @@ class RequestFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reindexRequest.getMaxDocs() != null) {
|
if (reindexRequest.getMaxDocs() != null) {
|
||||||
request.setMaxDocs(reindexRequest.getMaxDocs());
|
request.setMaxDocs(Math.toIntExact(reindexRequest.getMaxDocs()));
|
||||||
}
|
}
|
||||||
// region source build
|
// region source build
|
||||||
final Source source = reindexRequest.getSource();
|
final Source source = reindexRequest.getSource();
|
||||||
@ -464,12 +471,12 @@ class RequestFactory {
|
|||||||
|
|
||||||
final org.springframework.data.elasticsearch.annotations.Document.VersionType versionType = dest.getVersionType();
|
final org.springframework.data.elasticsearch.annotations.Document.VersionType versionType = dest.getVersionType();
|
||||||
if (versionType != null) {
|
if (versionType != null) {
|
||||||
request.setDestVersionType(VersionType.fromString(versionType.name().toLowerCase(Locale.ROOT)));
|
request.setDestVersionType(VersionType.fromString(versionType.getEsName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final IndexQuery.OpType opType = dest.getOpType();
|
final IndexQuery.OpType opType = dest.getOpType();
|
||||||
if (opType != null) {
|
if (opType != null) {
|
||||||
request.setDestOpType(opType.name().toLowerCase(Locale.ROOT));
|
request.setDestOpType(opType.getEsName());
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -508,7 +515,7 @@ class RequestFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reindexRequest.getSlices() != null) {
|
if (reindexRequest.getSlices() != null) {
|
||||||
request.setSlices(reindexRequest.getSlices());
|
request.setSlices(Math.toIntExact(reindexRequest.getSlices()));
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
return request;
|
return request;
|
||||||
@ -923,22 +930,6 @@ class RequestFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private void prepareSort(Query query, SearchRequestBuilder searchRequestBuilder,
|
|
||||||
@Nullable ElasticsearchPersistentEntity<?> entity) {
|
|
||||||
if (query.getSort() != null) {
|
|
||||||
query.getSort().forEach(order -> searchRequestBuilder.addSort(getSortBuilder(order, entity)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query instanceof NativeSearchQuery) {
|
|
||||||
NativeSearchQuery nativeSearchQuery = (NativeSearchQuery) query;
|
|
||||||
List<SortBuilder<?>> sorts = nativeSearchQuery.getElasticsearchSorts();
|
|
||||||
if (sorts != null) {
|
|
||||||
sorts.forEach(searchRequestBuilder::addSort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
|
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
|
||||||
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
|
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ public class ResponseConverter {
|
|||||||
|
|
||||||
MultiGetResponse.Failure responseFailure = itemResponse.getFailure();
|
MultiGetResponse.Failure responseFailure = itemResponse.getFailure();
|
||||||
return responseFailure != null ? MultiGetItem.Failure.of(responseFailure.getIndex(), responseFailure.getType(),
|
return responseFailure != null ? MultiGetItem.Failure.of(responseFailure.getIndex(), responseFailure.getType(),
|
||||||
responseFailure.getId(), responseFailure.getFailure()) : null;
|
responseFailure.getId(), responseFailure.getFailure(), null) : null;
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@ -387,8 +387,7 @@ public class ResponseConverter {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region postReindexResponse
|
// region reindex
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 4.4
|
* @since 4.4
|
||||||
*/
|
*/
|
||||||
@ -402,6 +401,7 @@ public class ResponseConverter {
|
|||||||
.withTook(bulkByScrollResponse.getTook().getMillis()) //
|
.withTook(bulkByScrollResponse.getTook().getMillis()) //
|
||||||
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
|
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
|
||||||
.withTotal(bulkByScrollResponse.getTotal()) //
|
.withTotal(bulkByScrollResponse.getTotal()) //
|
||||||
|
.withCreated(bulkByScrollResponse.getCreated()) //
|
||||||
.withUpdated(bulkByScrollResponse.getUpdated()) //
|
.withUpdated(bulkByScrollResponse.getUpdated()) //
|
||||||
.withDeleted(bulkByScrollResponse.getDeleted()) //
|
.withDeleted(bulkByScrollResponse.getDeleted()) //
|
||||||
.withBatches(bulkByScrollResponse.getBatches()) //
|
.withBatches(bulkByScrollResponse.getBatches()) //
|
||||||
|
@ -66,15 +66,18 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
|
|||||||
private static final Log LOGGER = LogFactory.getLog(RestIndexTemplate.class);
|
private static final Log LOGGER = LogFactory.getLog(RestIndexTemplate.class);
|
||||||
|
|
||||||
private final ElasticsearchRestTemplate restTemplate;
|
private final ElasticsearchRestTemplate restTemplate;
|
||||||
|
protected final RequestFactory requestFactory;
|
||||||
|
|
||||||
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
|
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
|
||||||
super(restTemplate.getElasticsearchConverter(), boundClass);
|
super(restTemplate.getElasticsearchConverter(), boundClass);
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
|
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
|
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
|
||||||
super(restTemplate.getElasticsearchConverter(), boundIndex);
|
super(restTemplate.getElasticsearchConverter(), boundIndex);
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
|
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -116,7 +119,7 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
|
|||||||
@Override
|
@Override
|
||||||
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
|
protected Map<String, Object> doGetMapping(IndexCoordinates index) {
|
||||||
|
|
||||||
Assert.notNull(index, "No index defined for getMapping()");
|
Assert.notNull(index, "No index defined for doGetMapping()");
|
||||||
|
|
||||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
|
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
|
||||||
|
|
||||||
|
@ -57,4 +57,18 @@ public class RuntimeField {
|
|||||||
map.put("script", script);
|
map.put("script", script);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public String getScript() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,11 @@ import java.util.Map;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
|
||||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||||
import org.springframework.data.elasticsearch.core.document.Document;
|
import org.springframework.data.elasticsearch.core.document.Document;
|
||||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
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.ElasticsearchPersistentEntity;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
|
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
|
||||||
|
@ -19,9 +19,10 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
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.Query;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
@ -77,7 +78,9 @@ public interface SearchOperations {
|
|||||||
* and get the suggest from {@link SearchHits#getSuggest()}
|
* and get the suggest from {@link SearchHits#getSuggest()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
|
default SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
|
||||||
|
throw new InvalidDataAccessApiUsageException("Unsupported operation");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does a suggest query
|
* Does a suggest query
|
||||||
@ -90,7 +93,9 @@ public interface SearchOperations {
|
|||||||
* and get the suggest from {@link SearchHits#getSuggest()}
|
* and get the suggest from {@link SearchHits#getSuggest()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
|
default SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||||
|
throw new InvalidDataAccessApiUsageException("Unsupported operation");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the query against elasticsearch and return the first returned object.
|
* Execute the query against elasticsearch and return the first returned object.
|
||||||
|
@ -122,13 +122,23 @@ public class ClusterHealth {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ClusterHealth{" + "clusterName='" + clusterName + '\'' + ", status='" + status + '\'' + ", numberOfNodes="
|
return "ClusterHealth{" + //
|
||||||
+ numberOfNodes + ", numberOfDataNodes=" + numberOfDataNodes + ", activeShards=" + activeShards
|
"clusterName='" + clusterName + '\'' + //
|
||||||
+ ", relocatingShards=" + relocatingShards + ", activePrimaryShards=" + activePrimaryShards
|
", status='" + status + '\'' + //
|
||||||
+ ", initializingShards=" + initializingShards + ", unassignedShards=" + unassignedShards
|
", numberOfNodes=" + numberOfNodes + //
|
||||||
+ ", activeShardsPercent=" + activeShardsPercent + ", numberOfPendingTasks=" + numberOfPendingTasks
|
", numberOfDataNodes=" + numberOfDataNodes + //
|
||||||
+ ", timedOut=" + timedOut + ", numberOfInFlightFetch=" + numberOfInFlightFetch + ", delayedUnassignedShards="
|
", activeShards=" + activeShards + //
|
||||||
+ delayedUnassignedShards + ", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + '}';
|
", relocatingShards=" + relocatingShards + //
|
||||||
|
", activePrimaryShards=" + activePrimaryShards + //
|
||||||
|
", initializingShards=" + initializingShards + //
|
||||||
|
", unassignedShards=" + unassignedShards + //
|
||||||
|
", activeShardsPercent=" + activeShardsPercent + //
|
||||||
|
", numberOfPendingTasks=" + numberOfPendingTasks + //
|
||||||
|
", timedOut=" + timedOut + //
|
||||||
|
", numberOfInFlightFetch=" + numberOfInFlightFetch + //
|
||||||
|
", delayedUnassignedShards=" + delayedUnassignedShards + //
|
||||||
|
", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + //
|
||||||
|
'}'; //
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClusterHealthBuilder builder() {
|
public static ClusterHealthBuilder builder() {
|
||||||
@ -160,7 +170,7 @@ public class ClusterHealth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ClusterHealthBuilder withStatus(String status) {
|
public ClusterHealthBuilder withStatus(String status) {
|
||||||
this.status = status;
|
this.status = status.toUpperCase();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -177,8 +178,8 @@ public final class DocumentAdapters {
|
|||||||
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
|
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
|
||||||
|
|
||||||
if (sourceInnerHits != null) {
|
if (sourceInnerHits != null) {
|
||||||
sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name,
|
sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponseBuilder.from(searchHits,
|
||||||
SearchDocumentResponse.from(searchHits, null, null, null, searchDocument -> null)));
|
null, null, null, searchDocument -> CompletableFuture.completedFuture(null))));
|
||||||
}
|
}
|
||||||
|
|
||||||
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
|
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
|
||||||
|
@ -23,16 +23,16 @@ import org.springframework.util.Assert;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* class that holds explanations returned from an Elasticsearch search.
|
* class that holds explanations returned from an Elasticsearch search.
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
*/
|
*/
|
||||||
public class Explanation {
|
public class Explanation {
|
||||||
private final boolean match;
|
@Nullable private final Boolean match;
|
||||||
private final Double value;
|
private final Double value;
|
||||||
@Nullable private final String description;
|
@Nullable private final String description;
|
||||||
private final List<Explanation> details;
|
private final List<Explanation> details;
|
||||||
|
|
||||||
public Explanation(boolean match, Double value, @Nullable String description, List<Explanation> details) {
|
public Explanation(@Nullable Boolean match, Double value, @Nullable String description, List<Explanation> details) {
|
||||||
|
|
||||||
Assert.notNull(value, "value must not be null");
|
Assert.notNull(value, "value must not be null");
|
||||||
Assert.notNull(details, "details must not be null");
|
Assert.notNull(details, "details must not be null");
|
||||||
@ -44,7 +44,7 @@ public class Explanation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMatch() {
|
public boolean isMatch() {
|
||||||
return match;
|
return match != null && match;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Double getValue() {
|
public Double getValue() {
|
||||||
|
@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core.document;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SearchDocument} implementation using a {@link Document} delegate.
|
||||||
|
*
|
||||||
|
* @author Mark Paluch
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class SearchDocumentAdapter implements SearchDocument {
|
||||||
|
|
||||||
|
private final float score;
|
||||||
|
private final Object[] sortValues;
|
||||||
|
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;
|
||||||
|
@Nullable private final Explanation explanation;
|
||||||
|
@Nullable private final List<String> matchedQueries;
|
||||||
|
@Nullable private final String routing;
|
||||||
|
|
||||||
|
public SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map<String, List<Object>> fields,
|
||||||
|
Map<String, List<String>> highlightFields, Map<String, SearchDocumentResponse> innerHits,
|
||||||
|
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List<String> matchedQueries,
|
||||||
|
@Nullable String routing) {
|
||||||
|
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.score = score;
|
||||||
|
this.sortValues = sortValues;
|
||||||
|
this.fields.putAll(fields);
|
||||||
|
this.highlightFields.putAll(highlightFields);
|
||||||
|
this.innerHits.putAll(innerHits);
|
||||||
|
this.nestedMetaData = nestedMetaData;
|
||||||
|
this.explanation = explanation;
|
||||||
|
this.matchedQueries = matchedQueries;
|
||||||
|
this.routing = routing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchDocument append(String key, Object value) {
|
||||||
|
delegate.append(key, value);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<Object>> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSortValues() {
|
||||||
|
return sortValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getHighlightFields() {
|
||||||
|
return highlightFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIndex() {
|
||||||
|
return delegate.getIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasId() {
|
||||||
|
return delegate.hasId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(String id) {
|
||||||
|
delegate.setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVersion() {
|
||||||
|
return delegate.hasVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVersion() {
|
||||||
|
return delegate.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVersion(long version) {
|
||||||
|
delegate.setVersion(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toJson() {
|
||||||
|
return delegate.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return delegate.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
return delegate.containsValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(Object key) {
|
||||||
|
|
||||||
|
if (delegate.containsKey(key)) {
|
||||||
|
return delegate.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to fields
|
||||||
|
return fields.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object put(String key, Object value) {
|
||||||
|
return delegate.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object remove(Object key) {
|
||||||
|
return delegate.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends String, ?> m) {
|
||||||
|
delegate.putAll(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return delegate.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Object> values() {
|
||||||
|
return delegate.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, Object>> entrySet() {
|
||||||
|
return delegate.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Explanation getExplanation() {
|
||||||
|
return explanation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public List<String> getMatchedQueries() {
|
||||||
|
return matchedQueries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof SearchDocumentAdapter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SearchDocumentAdapter that = (SearchDocumentAdapter) o;
|
||||||
|
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return delegate.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(BiConsumer<? super String, ? super Object> action) {
|
||||||
|
delegate.forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object key, Object value) {
|
||||||
|
return delegate.remove(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRouting() {
|
||||||
|
return routing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
String id = hasId() ? getId() : "?";
|
||||||
|
String version = hasVersion() ? Long.toString(getVersion()) : "?";
|
||||||
|
|
||||||
|
return getClass().getSimpleName() + '@' + id + '#' + version + ' ' + toJson();
|
||||||
|
}
|
||||||
|
}
|
@ -15,57 +15,39 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.document;
|
package org.springframework.data.elasticsearch.core.document;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.apache.lucene.search.TotalHits;
|
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
|
||||||
import org.elasticsearch.common.text.Text;
|
|
||||||
import org.elasticsearch.search.SearchHit;
|
|
||||||
import org.elasticsearch.search.SearchHits;
|
|
||||||
import org.elasticsearch.search.aggregations.Aggregations;
|
|
||||||
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchAggregations;
|
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
|
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion;
|
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.SortBy;
|
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||||
import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion;
|
|
||||||
import org.springframework.data.elasticsearch.support.ScoreDoc;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
|
* This represents the complete search response from Elasticsearch, including the returned documents.
|
||||||
* created with the {@link #from(SearchResponse, EntityCreator)} method.
|
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public class SearchDocumentResponse {
|
public class SearchDocumentResponse {
|
||||||
|
|
||||||
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
|
|
||||||
|
|
||||||
private final long totalHits;
|
private final long totalHits;
|
||||||
private final String totalHitsRelation;
|
private final String totalHitsRelation;
|
||||||
private final float maxScore;
|
private final float maxScore;
|
||||||
private final String scrollId;
|
@Nullable private final String scrollId;
|
||||||
private final List<SearchDocument> searchDocuments;
|
private final List<SearchDocument> searchDocuments;
|
||||||
@Nullable private final AggregationsContainer<?> aggregations;
|
@Nullable private final AggregationsContainer<?> aggregations;
|
||||||
@Nullable private final Suggest suggest;
|
@Nullable private final Suggest suggest;
|
||||||
|
|
||||||
private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId,
|
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||||
List<SearchDocument> searchDocuments, @Nullable Aggregations aggregations, @Nullable Suggest suggest) {
|
List<SearchDocument> searchDocuments, @Nullable AggregationsContainer<?> aggregationsContainer,
|
||||||
|
@Nullable Suggest suggest) {
|
||||||
this.totalHits = totalHits;
|
this.totalHits = totalHits;
|
||||||
this.totalHitsRelation = totalHitsRelation;
|
this.totalHitsRelation = totalHitsRelation;
|
||||||
this.maxScore = maxScore;
|
this.maxScore = maxScore;
|
||||||
this.scrollId = scrollId;
|
this.scrollId = scrollId;
|
||||||
this.searchDocuments = searchDocuments;
|
this.searchDocuments = searchDocuments;
|
||||||
this.aggregations = aggregations != null ? new ElasticsearchAggregations(aggregations) : null;
|
this.aggregations = aggregationsContainer;
|
||||||
this.suggest = suggest;
|
this.suggest = suggest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +63,7 @@ public class SearchDocumentResponse {
|
|||||||
return maxScore;
|
return maxScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getScrollId() {
|
public String getScrollId() {
|
||||||
return scrollId;
|
return scrollId;
|
||||||
}
|
}
|
||||||
@ -99,178 +82,6 @@ public class SearchDocumentResponse {
|
|||||||
return suggest;
|
return suggest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
|
||||||
*
|
|
||||||
* @param searchResponse must not be {@literal null}
|
|
||||||
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
|
||||||
* @param <T> entity type
|
|
||||||
* @return the SearchDocumentResponse
|
|
||||||
*/
|
|
||||||
public static <T> SearchDocumentResponse from(SearchResponse searchResponse, EntityCreator<T> entityCreator) {
|
|
||||||
|
|
||||||
Assert.notNull(searchResponse, "searchResponse must not be null");
|
|
||||||
|
|
||||||
SearchHits searchHits = searchResponse.getHits();
|
|
||||||
String scrollId = searchResponse.getScrollId();
|
|
||||||
Aggregations aggregations = searchResponse.getAggregations();
|
|
||||||
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
|
|
||||||
|
|
||||||
return from(searchHits, scrollId, aggregations, suggest, entityCreator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a {@link SearchDocumentResponse} from {@link SearchHits} with the given scrollId aggregations and suggest
|
|
||||||
*
|
|
||||||
* @param searchHits the {@link SearchHits} to process
|
|
||||||
* @param scrollId scrollId
|
|
||||||
* @param aggregations aggregations
|
|
||||||
* @param suggestES the suggestion response from Elasticsearch
|
|
||||||
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
|
||||||
* @param <T> entity type
|
|
||||||
* @return the {@link SearchDocumentResponse}
|
|
||||||
* @since 4.3
|
|
||||||
*/
|
|
||||||
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
|
|
||||||
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
|
|
||||||
EntityCreator<T> entityCreator) {
|
|
||||||
|
|
||||||
TotalHits responseTotalHits = searchHits.getTotalHits();
|
|
||||||
|
|
||||||
long totalHits;
|
|
||||||
String totalHitsRelation;
|
|
||||||
|
|
||||||
if (responseTotalHits != null) {
|
|
||||||
totalHits = responseTotalHits.value;
|
|
||||||
totalHitsRelation = responseTotalHits.relation.name();
|
|
||||||
} else {
|
|
||||||
totalHits = searchHits.getHits().length;
|
|
||||||
totalHitsRelation = "OFF";
|
|
||||||
}
|
|
||||||
|
|
||||||
float maxScore = searchHits.getMaxScore();
|
|
||||||
|
|
||||||
List<SearchDocument> searchDocuments = new ArrayList<>();
|
|
||||||
for (SearchHit searchHit : searchHits) {
|
|
||||||
if (searchHit != null) {
|
|
||||||
searchDocuments.add(DocumentAdapters.from(searchHit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
|
||||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
|
|
||||||
suggest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
|
|
||||||
EntityCreator<T> entityCreator) {
|
|
||||||
|
|
||||||
if (suggestES == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>> suggestions = new ArrayList<>();
|
|
||||||
|
|
||||||
for (org.elasticsearch.search.suggest.Suggest.Suggestion<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option>> suggestionES : suggestES) {
|
|
||||||
|
|
||||||
if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) {
|
|
||||||
org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES;
|
|
||||||
|
|
||||||
List<TermSuggestion.Entry> entries = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) {
|
|
||||||
|
|
||||||
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option optionES : entryES) {
|
|
||||||
options.add(new TermSuggestion.Entry.Option(textToString(optionES.getText()),
|
|
||||||
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
|
|
||||||
optionES.getFreq()));
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.add(new TermSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
|
|
||||||
entryES.getLength(), options));
|
|
||||||
}
|
|
||||||
|
|
||||||
suggestions.add(new TermSuggestion(termSuggestionES.getName(), termSuggestionES.getSize(), entries,
|
|
||||||
suggestFrom(termSuggestionES.getSort())));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) {
|
|
||||||
org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES;
|
|
||||||
|
|
||||||
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) {
|
|
||||||
|
|
||||||
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry.Option optionES : entryES) {
|
|
||||||
options.add(new PhraseSuggestion.Entry.Option(textToString(optionES.getText()),
|
|
||||||
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch()));
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.add(new PhraseSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
|
|
||||||
entryES.getLength(), options, entryES.getCutoffScore()));
|
|
||||||
}
|
|
||||||
|
|
||||||
suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) {
|
|
||||||
org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES;
|
|
||||||
|
|
||||||
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) {
|
|
||||||
|
|
||||||
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
|
|
||||||
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
|
|
||||||
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
|
|
||||||
|
|
||||||
T hitEntity = null;
|
|
||||||
|
|
||||||
if (searchDocument != null) {
|
|
||||||
try {
|
|
||||||
hitEntity = entityCreator.apply(searchDocument).get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (LOGGER.isWarnEnabled()) {
|
|
||||||
LOGGER.warn("Error creating entity from SearchDocument");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()),
|
|
||||||
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
|
|
||||||
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.add(new CompletionSuggestion.Entry<T>(textToString(entryES.getText()), entryES.getOffset(),
|
|
||||||
entryES.getLength(), options));
|
|
||||||
}
|
|
||||||
|
|
||||||
suggestions.add(
|
|
||||||
new CompletionSuggestion<T>(completionSuggestionES.getName(), completionSuggestionES.getSize(), entries));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Suggest(suggestions, suggestES.hasScoreDocs());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SortBy suggestFrom(org.elasticsearch.search.suggest.SortBy sort) {
|
|
||||||
return SortBy.valueOf(sort.name().toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static ScoreDoc scoreDocFrom(@Nullable org.apache.lucene.search.ScoreDoc scoreDoc) {
|
|
||||||
|
|
||||||
if (scoreDoc == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ScoreDoc(scoreDoc.score, scoreDoc.doc, scoreDoc.shardIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String textToString(@Nullable Text text) {
|
|
||||||
return text != null ? text.string() : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
|
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
|
||||||
* imperative and the reactive code.
|
* imperative and the reactive code.
|
||||||
|
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core.document;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.lucene.search.TotalHits;
|
||||||
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
|
import org.elasticsearch.search.SearchHits;
|
||||||
|
import org.elasticsearch.search.aggregations.Aggregations;
|
||||||
|
import org.springframework.data.elasticsearch.core.ElasticsearchAggregations;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.SortBy;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
|
||||||
|
import org.springframework.data.elasticsearch.core.suggest.response.TermSuggestion;
|
||||||
|
import org.springframework.data.elasticsearch.support.ScoreDoc;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class to create {@link SearchDocumentResponse} instances.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.0
|
||||||
|
*/
|
||||||
|
public class SearchDocumentResponseBuilder {
|
||||||
|
|
||||||
|
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
||||||
|
*
|
||||||
|
* @param searchResponse must not be {@literal null}
|
||||||
|
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
||||||
|
* @param <T> entity type
|
||||||
|
* @return the SearchDocumentResponse
|
||||||
|
*/
|
||||||
|
public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||||
|
|
||||||
|
Assert.notNull(searchResponse, "searchResponse must not be null");
|
||||||
|
|
||||||
|
SearchHits searchHits = searchResponse.getHits();
|
||||||
|
String scrollId = searchResponse.getScrollId();
|
||||||
|
Aggregations aggregations = searchResponse.getAggregations();
|
||||||
|
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
|
||||||
|
|
||||||
|
return from(searchHits, scrollId, aggregations, suggest, entityCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a {@link SearchDocumentResponseBuilder} from {@link SearchHits} with the given scrollId aggregations and
|
||||||
|
* suggest
|
||||||
|
*
|
||||||
|
* @param searchHits the {@link SearchHits} to process
|
||||||
|
* @param scrollId scrollId
|
||||||
|
* @param aggregations aggregations
|
||||||
|
* @param suggestES the suggestion response from Elasticsearch
|
||||||
|
* @param entityCreator function to create an entity from a {@link SearchDocument}
|
||||||
|
* @param <T> entity type
|
||||||
|
* @return the {@link SearchDocumentResponse}
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
|
||||||
|
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||||
|
|
||||||
|
TotalHits responseTotalHits = searchHits.getTotalHits();
|
||||||
|
|
||||||
|
long totalHits;
|
||||||
|
String totalHitsRelation;
|
||||||
|
|
||||||
|
if (responseTotalHits != null) {
|
||||||
|
totalHits = responseTotalHits.value;
|
||||||
|
totalHitsRelation = responseTotalHits.relation.name();
|
||||||
|
} else {
|
||||||
|
totalHits = searchHits.getHits().length;
|
||||||
|
totalHitsRelation = "OFF";
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxScore = searchHits.getMaxScore();
|
||||||
|
|
||||||
|
List<SearchDocument> searchDocuments = new ArrayList<>();
|
||||||
|
for (SearchHit searchHit : searchHits) {
|
||||||
|
if (searchHit != null) {
|
||||||
|
searchDocuments.add(DocumentAdapters.from(searchHit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElasticsearchAggregations aggregationsContainer = aggregations != null ? new ElasticsearchAggregations(aggregations)
|
||||||
|
: null;
|
||||||
|
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
||||||
|
|
||||||
|
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments,
|
||||||
|
aggregationsContainer, suggest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
|
||||||
|
SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||||
|
|
||||||
|
if (suggestES == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>> suggestions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (org.elasticsearch.search.suggest.Suggest.Suggestion<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry<? extends org.elasticsearch.search.suggest.Suggest.Suggestion.Entry.Option>> suggestionES : suggestES) {
|
||||||
|
|
||||||
|
if (suggestionES instanceof org.elasticsearch.search.suggest.term.TermSuggestion) {
|
||||||
|
org.elasticsearch.search.suggest.term.TermSuggestion termSuggestionES = (org.elasticsearch.search.suggest.term.TermSuggestion) suggestionES;
|
||||||
|
|
||||||
|
List<TermSuggestion.Entry> entries = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry entryES : termSuggestionES) {
|
||||||
|
|
||||||
|
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.term.TermSuggestion.Entry.Option optionES : entryES) {
|
||||||
|
options.add(new TermSuggestion.Entry.Option(textToString(optionES.getText()),
|
||||||
|
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
|
||||||
|
optionES.getFreq()));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(new TermSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
|
||||||
|
entryES.getLength(), options));
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions.add(new TermSuggestion(termSuggestionES.getName(), termSuggestionES.getSize(), entries,
|
||||||
|
suggestFrom(termSuggestionES.getSort())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestionES instanceof org.elasticsearch.search.suggest.phrase.PhraseSuggestion) {
|
||||||
|
org.elasticsearch.search.suggest.phrase.PhraseSuggestion phraseSuggestionES = (org.elasticsearch.search.suggest.phrase.PhraseSuggestion) suggestionES;
|
||||||
|
|
||||||
|
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry entryES : phraseSuggestionES) {
|
||||||
|
|
||||||
|
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.phrase.PhraseSuggestion.Entry.Option optionES : entryES) {
|
||||||
|
options.add(new PhraseSuggestion.Entry.Option(textToString(optionES.getText()),
|
||||||
|
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch()));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(new PhraseSuggestion.Entry(textToString(entryES.getText()), entryES.getOffset(),
|
||||||
|
entryES.getLength(), options, entryES.getCutoffScore()));
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions.add(new PhraseSuggestion(phraseSuggestionES.getName(), phraseSuggestionES.getSize(), entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestionES instanceof org.elasticsearch.search.suggest.completion.CompletionSuggestion) {
|
||||||
|
org.elasticsearch.search.suggest.completion.CompletionSuggestion completionSuggestionES = (org.elasticsearch.search.suggest.completion.CompletionSuggestion) suggestionES;
|
||||||
|
|
||||||
|
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry entryES : completionSuggestionES) {
|
||||||
|
|
||||||
|
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
|
||||||
|
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
|
||||||
|
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
|
||||||
|
T hitEntity = null;
|
||||||
|
|
||||||
|
if (searchDocument != null) {
|
||||||
|
try {
|
||||||
|
hitEntity = entityCreator.apply(searchDocument).get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (LOGGER.isWarnEnabled()) {
|
||||||
|
LOGGER.warn("Error creating entity from SearchDocument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.add(new CompletionSuggestion.Entry.Option<>(textToString(optionES.getText()),
|
||||||
|
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
|
||||||
|
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.add(new CompletionSuggestion.Entry<>(textToString(entryES.getText()), entryES.getOffset(),
|
||||||
|
entryES.getLength(), options));
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions.add(
|
||||||
|
new CompletionSuggestion<>(completionSuggestionES.getName(), completionSuggestionES.getSize(), entries));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Suggest(suggestions, suggestES.hasScoreDocs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SortBy suggestFrom(org.elasticsearch.search.suggest.SortBy sort) {
|
||||||
|
return SortBy.valueOf(sort.name().toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ScoreDoc scoreDocFrom(@Nullable org.apache.lucene.search.ScoreDoc scoreDoc) {
|
||||||
|
|
||||||
|
if (scoreDoc == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScoreDoc(scoreDoc.score, scoreDoc.doc, scoreDoc.shardIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String textToString(@Nullable Text text) {
|
||||||
|
return text != null ? text.string() : "";
|
||||||
|
}
|
||||||
|
}
|
@ -73,7 +73,7 @@ import com.fasterxml.jackson.databind.util.RawValue;
|
|||||||
*/
|
*/
|
||||||
public class MappingBuilder {
|
public class MappingBuilder {
|
||||||
|
|
||||||
private static final Log LOGGER = LogFactory.getLog(ElasticsearchRestTemplate.class);
|
private static final Log LOGGER = LogFactory.getLog(MappingBuilder.class);
|
||||||
|
|
||||||
private static final String FIELD_INDEX = "index";
|
private static final String FIELD_INDEX = "index";
|
||||||
private static final String FIELD_PROPERTIES = "properties";
|
private static final String FIELD_PROPERTIES = "properties";
|
||||||
|
@ -15,9 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.index;
|
package org.springframework.data.elasticsearch.core.index;
|
||||||
|
|
||||||
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
import java.util.AbstractMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class defining the settings for an index.
|
* class defining the settings for an index.
|
||||||
@ -47,4 +52,77 @@ public class Settings extends DefaultStringObjectMap<Settings> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "Settings: " + toJson();
|
return "Settings: " + toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(Object key) {
|
||||||
|
return containsKey(key) ? super.get(key) : path(key.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges some other settings onto this one. Other has higher priority on same keys.
|
||||||
|
*
|
||||||
|
* @param other the other settings. Must not be {@literal null}
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public void merge(Settings other) {
|
||||||
|
|
||||||
|
Assert.notNull(other, "other must not be null");
|
||||||
|
|
||||||
|
deepMerge(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* taken from https://stackoverflow.com/a/29698326/4393565
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Map<?, ?> deepMerge(Map<String, Object> original, Map<String, Object> newMap) {
|
||||||
|
for (Object key : newMap.keySet()) {
|
||||||
|
if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) {
|
||||||
|
Map<String, Object> originalChild = (Map<String, Object>) original.get(key);
|
||||||
|
Map<String, Object> newChild = (Map<String, Object>) newMap.get(key);
|
||||||
|
original.put(key.toString(), deepMerge(originalChild, newChild));
|
||||||
|
} else if (newMap.get(key) instanceof List && original.get(key) instanceof List) {
|
||||||
|
List<Object> originalChild = (List<Object>) original.get(key);
|
||||||
|
List<Object> newChild = (List<Object>) newMap.get(key);
|
||||||
|
for (Object each : newChild) {
|
||||||
|
if (!originalChild.contains(each)) {
|
||||||
|
originalChild.add(each);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original.put(key.toString(), newMap.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flattens the nested structure (JSON fields index/foo/bar/: value) into a flat structure (index.foo.bar: value)
|
||||||
|
*
|
||||||
|
* @return Settings with the flattened elements.
|
||||||
|
*/
|
||||||
|
public Settings flatten() {
|
||||||
|
return new Settings( //
|
||||||
|
entrySet().stream() //
|
||||||
|
.flatMap(Settings::doFlatten) //
|
||||||
|
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))); //
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* flattens a Map<String, Object> to a stream of Map.Entry objects where the keys are the dot separated concatenated
|
||||||
|
* keys of sub map entries
|
||||||
|
*/
|
||||||
|
static private Stream<Map.Entry<String, Object>> doFlatten(Map.Entry<String, Object> entry) {
|
||||||
|
|
||||||
|
if (entry.getValue() instanceof Map<?, ?>) {
|
||||||
|
Map<?, ?> nested = (Map<?, ?>) entry.getValue();
|
||||||
|
|
||||||
|
// noinspection unchecked
|
||||||
|
return nested.entrySet().stream() //
|
||||||
|
.map(e -> new AbstractMap.SimpleEntry<>(entry.getKey() + "." + e.getKey(), e.getValue()))
|
||||||
|
.flatMap(e2 -> doFlatten((Entry<String, Object>) e2));
|
||||||
|
} else {
|
||||||
|
return Stream.of(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,23 @@ public class BaseQuery implements Query {
|
|||||||
private List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
private List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
||||||
private final List<RuntimeField> runtimeFields = new ArrayList<>();
|
private final List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||||
|
|
||||||
|
public BaseQuery() {}
|
||||||
|
|
||||||
|
public <Q extends BaseQuery, B extends BaseQueryBuilder<Q, B>> BaseQuery(BaseQueryBuilder<Q, B> builder) {
|
||||||
|
this.sort = builder.getSort();
|
||||||
|
// do a setPageable after setting the sort, because the pageable may contain an additional sort
|
||||||
|
this.setPageable(builder.getPageable() != null ? builder.getPageable() : DEFAULT_PAGE);
|
||||||
|
this.ids = builder.getIds();
|
||||||
|
this.trackScores = builder.getTrackScores();
|
||||||
|
this.maxResults = builder.getMaxResults();
|
||||||
|
this.indicesOptions = builder.getIndicesOptions();
|
||||||
|
this.minScore = builder.getMinScore();
|
||||||
|
this.preference = builder.getPreference();
|
||||||
|
this.sourceFilter = builder.getSourceFilter();
|
||||||
|
this.fields = builder.getFields();
|
||||||
|
// #1973 add the other fields to the builder
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Sort getSort() {
|
public Sort getSort() {
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core.query;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base class for query builders. The different implementations of {@link Query} should derive from this class and then
|
||||||
|
* offer a constructor that takes their builder as argument and passes this on to the super class.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQueryBuilder<Q, SELF>> {
|
||||||
|
|
||||||
|
@Nullable private Pageable pageable;
|
||||||
|
@Nullable private Sort sort;
|
||||||
|
@Nullable private Integer maxResults;
|
||||||
|
@Nullable private Collection<String> ids;
|
||||||
|
private boolean trackScores;
|
||||||
|
@Nullable protected IndicesOptions indicesOptions;
|
||||||
|
private float minScore;
|
||||||
|
@Nullable private String preference;
|
||||||
|
@Nullable private SourceFilter sourceFilter;
|
||||||
|
private List<String> fields = new ArrayList<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Pageable getPageable() {
|
||||||
|
return pageable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Sort getSort() {
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Integer getMaxResults() {
|
||||||
|
return maxResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Collection<String> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getTrackScores() {
|
||||||
|
return trackScores;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public IndicesOptions getIndicesOptions() {
|
||||||
|
return indicesOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMinScore() {
|
||||||
|
return minScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getPreference() {
|
||||||
|
return preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public SourceFilter getSourceFilter() {
|
||||||
|
return sourceFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withPageable(Pageable pageable) {
|
||||||
|
this.pageable = pageable;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withSort(Sort sort) {
|
||||||
|
if (this.sort == null) {
|
||||||
|
this.sort = sort;
|
||||||
|
} else {
|
||||||
|
this.sort = this.sort.and(sort);
|
||||||
|
}
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withMaxResults(Integer maxResults) {
|
||||||
|
this.maxResults = maxResults;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withIds(String... ids) {
|
||||||
|
this.ids = Arrays.asList(ids);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withIds(Collection<String> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withTrackScores(boolean trackScores) {
|
||||||
|
this.trackScores = trackScores;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withIndicesOptions(IndicesOptions indicesOptions) {
|
||||||
|
this.indicesOptions = indicesOptions;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withMinScore(float minScore) {
|
||||||
|
this.minScore = minScore;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withPreference(String preference) {
|
||||||
|
this.preference = preference;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withSourceFilter(SourceFilter sourceFilter) {
|
||||||
|
this.sourceFilter = sourceFilter;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withFields(String... fields) {
|
||||||
|
Collections.addAll(this.fields, fields);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF withFields(Collection<String> fields) {
|
||||||
|
this.fields.addAll(fields);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Q build();
|
||||||
|
|
||||||
|
private SELF self() {
|
||||||
|
// noinspection unchecked
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.query;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,9 +174,11 @@ public class ByQueryResponse {
|
|||||||
@Nullable private final Long seqNo;
|
@Nullable private final Long seqNo;
|
||||||
@Nullable private final Long term;
|
@Nullable private final Long term;
|
||||||
@Nullable private final Boolean aborted;
|
@Nullable private final Boolean aborted;
|
||||||
|
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause,
|
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause,
|
||||||
@Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted) {
|
@Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted,
|
||||||
|
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -184,6 +187,7 @@ public class ByQueryResponse {
|
|||||||
this.seqNo = seqNo;
|
this.seqNo = seqNo;
|
||||||
this.term = term;
|
this.term = term;
|
||||||
this.aborted = aborted;
|
this.aborted = aborted;
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -247,6 +251,7 @@ public class ByQueryResponse {
|
|||||||
@Nullable private Long seqNo;
|
@Nullable private Long seqNo;
|
||||||
@Nullable private Long term;
|
@Nullable private Long term;
|
||||||
@Nullable private Boolean aborted;
|
@Nullable private Boolean aborted;
|
||||||
|
@Nullable private ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
private FailureBuilder() {}
|
private FailureBuilder() {}
|
||||||
|
|
||||||
@ -290,8 +295,13 @@ public class ByQueryResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FailureBuilder withErrorCause(ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Failure build() {
|
public Failure build() {
|
||||||
return new Failure(index, type, id, cause, status, seqNo, term, aborted);
|
return new Failure(index, type, id, cause, status, seqNo, term, aborted, elasticsearchErrorCause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,19 @@ import org.springframework.util.Assert;
|
|||||||
*/
|
*/
|
||||||
public class CriteriaQuery extends BaseQuery {
|
public class CriteriaQuery extends BaseQuery {
|
||||||
|
|
||||||
private Criteria criteria;
|
private final Criteria criteria;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public CriteriaQuery(CriteriaQueryBuilder builder) {
|
||||||
|
super(builder);
|
||||||
|
this.criteria = builder.getCriteria();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CriteriaQueryBuilder builder(Criteria criteria) {
|
||||||
|
return new CriteriaQueryBuilder(criteria);
|
||||||
|
}
|
||||||
|
|
||||||
public CriteriaQuery(Criteria criteria) {
|
public CriteriaQuery(Criteria criteria) {
|
||||||
this(criteria, Pageable.unpaged());
|
this(criteria, Pageable.unpaged());
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core.query;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class CriteriaQueryBuilder extends BaseQueryBuilder<CriteriaQuery, CriteriaQueryBuilder> {
|
||||||
|
|
||||||
|
private final Criteria criteria;
|
||||||
|
|
||||||
|
public CriteriaQueryBuilder(Criteria criteria) {
|
||||||
|
|
||||||
|
Assert.notNull(criteria, "criteria must not be null");
|
||||||
|
|
||||||
|
this.criteria = criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Criteria getCriteria() {
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CriteriaQuery build() {
|
||||||
|
return new CriteriaQuery(this);
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ import org.springframework.lang.Nullable;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the {@link Highlight} annotation from a method to an Elasticsearch {@link HighlightBuilder}.
|
* Converts the {@link Highlight} annotation from a method to an Elasticsearch 7 {@link HighlightBuilder}.
|
||||||
*
|
*
|
||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
*/
|
*/
|
||||||
|
@ -168,6 +168,16 @@ public class IndexQuery {
|
|||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
public enum OpType {
|
public enum OpType {
|
||||||
INDEX, CREATE
|
INDEX("index"), CREATE("create");
|
||||||
|
|
||||||
|
private final String esName;
|
||||||
|
|
||||||
|
OpType(String esName) {
|
||||||
|
this.esName = esName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEsName() {
|
||||||
|
return esName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,6 @@ public class NativeSearchQuery extends BaseQuery {
|
|||||||
@Nullable private List<SearchExtBuilder> searchExtBuilders;
|
@Nullable private List<SearchExtBuilder> searchExtBuilders;
|
||||||
|
|
||||||
public NativeSearchQuery(@Nullable QueryBuilder query) {
|
public NativeSearchQuery(@Nullable QueryBuilder query) {
|
||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +98,17 @@ public class NativeSearchQuery extends BaseQuery {
|
|||||||
this.highlightFields = highlightFields;
|
this.highlightFields = highlightFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NativeSearchQuery(NativeSearchQueryBuilder builder, @Nullable QueryBuilder query,
|
||||||
|
@Nullable QueryBuilder filter, @Nullable List<SortBuilder<?>> sorts, @Nullable HighlightBuilder highlightBuilder,
|
||||||
|
@Nullable HighlightBuilder.Field[] highlightFields) {
|
||||||
|
super(builder);
|
||||||
|
this.query = query;
|
||||||
|
this.filter = filter;
|
||||||
|
this.sorts = sorts;
|
||||||
|
this.highlightBuilder = highlightBuilder;
|
||||||
|
this.highlightFields = highlightFields;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public QueryBuilder getQuery() {
|
public QueryBuilder getQuery() {
|
||||||
return query;
|
return query;
|
||||||
|
@ -32,7 +32,6 @@ import org.elasticsearch.search.collapse.CollapseBuilder;
|
|||||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||||
import org.elasticsearch.search.sort.SortBuilder;
|
import org.elasticsearch.search.sort.SortBuilder;
|
||||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +51,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author vdisk
|
* @author vdisk
|
||||||
* @author owen.qq
|
* @author owen.qq
|
||||||
*/
|
*/
|
||||||
public class NativeSearchQueryBuilder {
|
public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery, NativeSearchQueryBuilder> {
|
||||||
|
|
||||||
@Nullable private QueryBuilder queryBuilder;
|
@Nullable private QueryBuilder queryBuilder;
|
||||||
@Nullable private QueryBuilder filterBuilder;
|
@Nullable private QueryBuilder filterBuilder;
|
||||||
@ -62,21 +61,12 @@ public class NativeSearchQueryBuilder {
|
|||||||
private final List<PipelineAggregationBuilder> pipelineAggregationBuilders = new ArrayList<>();
|
private final List<PipelineAggregationBuilder> pipelineAggregationBuilders = new ArrayList<>();
|
||||||
@Nullable private HighlightBuilder highlightBuilder;
|
@Nullable private HighlightBuilder highlightBuilder;
|
||||||
@Nullable private List<HighlightBuilder.Field> highlightFields = new ArrayList<>();
|
@Nullable private List<HighlightBuilder.Field> highlightFields = new ArrayList<>();
|
||||||
private Pageable pageable = Pageable.unpaged();
|
|
||||||
@Nullable private List<String> fields = new ArrayList<>();
|
|
||||||
@Nullable protected List<String> storedFields;
|
@Nullable protected List<String> storedFields;
|
||||||
@Nullable private SourceFilter sourceFilter;
|
|
||||||
@Nullable private CollapseBuilder collapseBuilder;
|
@Nullable private CollapseBuilder collapseBuilder;
|
||||||
@Nullable private List<IndexBoost> indicesBoost = new ArrayList<>();
|
@Nullable private List<IndexBoost> indicesBoost = new ArrayList<>();
|
||||||
@Nullable private SearchTemplateRequestBuilder searchTemplateBuilder;
|
@Nullable private SearchTemplateRequestBuilder searchTemplateBuilder;
|
||||||
private float minScore;
|
|
||||||
private boolean trackScores;
|
|
||||||
@Nullable private List<String> ids = new ArrayList<>();
|
|
||||||
@Nullable private String route;
|
@Nullable private String route;
|
||||||
@Nullable private SearchType searchType;
|
@Nullable private SearchType searchType;
|
||||||
@Nullable private IndicesOptions indicesOptions;
|
|
||||||
@Nullable private String preference;
|
|
||||||
@Nullable private Integer maxResults;
|
|
||||||
@Nullable private Boolean trackTotalHits;
|
@Nullable private Boolean trackTotalHits;
|
||||||
@Nullable private Duration timeout;
|
@Nullable private Duration timeout;
|
||||||
private final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
private final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
||||||
@ -223,24 +213,6 @@ public class NativeSearchQueryBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withPageable(Pageable pageable) {
|
|
||||||
this.pageable = pageable;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 4.3
|
|
||||||
*/
|
|
||||||
public NativeSearchQueryBuilder withFields(Collection<String> fields) {
|
|
||||||
this.fields.addAll(fields);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withFields(String... fields) {
|
|
||||||
Collections.addAll(this.fields, fields);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withStoredFields(Collection<String> storedFields) {
|
public NativeSearchQueryBuilder withStoredFields(Collection<String> storedFields) {
|
||||||
|
|
||||||
if (this.storedFields == null) {
|
if (this.storedFields == null) {
|
||||||
@ -260,39 +232,6 @@ public class NativeSearchQueryBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withSourceFilter(SourceFilter sourceFilter) {
|
|
||||||
this.sourceFilter = sourceFilter;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withMinScore(float minScore) {
|
|
||||||
this.minScore = minScore;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param trackScores whether to track scores.
|
|
||||||
* @return this object
|
|
||||||
* @since 3.1
|
|
||||||
*/
|
|
||||||
public NativeSearchQueryBuilder withTrackScores(boolean trackScores) {
|
|
||||||
this.trackScores = trackScores;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withIds(Collection<String> ids) {
|
|
||||||
this.ids.addAll(ids);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 4.3
|
|
||||||
*/
|
|
||||||
public NativeSearchQueryBuilder withIds(String... ids) {
|
|
||||||
Collections.addAll(this.ids, ids);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withRoute(String route) {
|
public NativeSearchQueryBuilder withRoute(String route) {
|
||||||
this.route = route;
|
this.route = route;
|
||||||
return this;
|
return this;
|
||||||
@ -308,16 +247,6 @@ public class NativeSearchQueryBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withPreference(String preference) {
|
|
||||||
this.preference = preference;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NativeSearchQueryBuilder withMaxResults(Integer maxResults) {
|
|
||||||
this.maxResults = maxResults;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
@ -356,27 +285,17 @@ public class NativeSearchQueryBuilder {
|
|||||||
public NativeSearchQuery build() {
|
public NativeSearchQuery build() {
|
||||||
|
|
||||||
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( //
|
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( //
|
||||||
|
this, //
|
||||||
queryBuilder, //
|
queryBuilder, //
|
||||||
filterBuilder, //
|
filterBuilder, //
|
||||||
sortBuilders, //
|
sortBuilders, //
|
||||||
highlightBuilder, //
|
highlightBuilder, //
|
||||||
highlightFields.toArray(new HighlightBuilder.Field[highlightFields.size()]));
|
highlightFields.toArray(new HighlightBuilder.Field[highlightFields.size()]));
|
||||||
|
|
||||||
nativeSearchQuery.setPageable(pageable);
|
|
||||||
nativeSearchQuery.setTrackScores(trackScores);
|
|
||||||
|
|
||||||
if (fields != null) {
|
|
||||||
nativeSearchQuery.setFields(fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storedFields != null) {
|
if (storedFields != null) {
|
||||||
nativeSearchQuery.setStoredFields(storedFields);
|
nativeSearchQuery.setStoredFields(storedFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceFilter != null) {
|
|
||||||
nativeSearchQuery.addSourceFilter(sourceFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indicesBoost != null) {
|
if (indicesBoost != null) {
|
||||||
nativeSearchQuery.setIndicesBoost(indicesBoost);
|
nativeSearchQuery.setIndicesBoost(indicesBoost);
|
||||||
}
|
}
|
||||||
@ -401,14 +320,6 @@ public class NativeSearchQueryBuilder {
|
|||||||
nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders);
|
nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minScore > 0) {
|
|
||||||
nativeSearchQuery.setMinScore(minScore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids != null) {
|
|
||||||
nativeSearchQuery.setIds(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route != null) {
|
if (route != null) {
|
||||||
nativeSearchQuery.setRoute(route);
|
nativeSearchQuery.setRoute(route);
|
||||||
}
|
}
|
||||||
@ -421,14 +332,6 @@ public class NativeSearchQueryBuilder {
|
|||||||
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preference != null) {
|
|
||||||
nativeSearchQuery.setPreference(preference);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxResults != null) {
|
|
||||||
nativeSearchQuery.setMaxResults(maxResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeSearchQuery.setTrackTotalHits(trackTotalHits);
|
nativeSearchQuery.setTrackTotalHits(trackTotalHits);
|
||||||
|
|
||||||
if (timeout != null) {
|
if (timeout != null) {
|
||||||
|
@ -339,7 +339,7 @@ public interface Query {
|
|||||||
void setScrollTime(@Nullable Duration scrollTime);
|
void setScrollTime(@Nullable Duration scrollTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@literal true} if scrollTimeMillis is set.
|
* @return {@literal true} if a scroll time is set.
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
default boolean hasScrollTime() {
|
default boolean hasScrollTime() {
|
||||||
|
@ -23,10 +23,11 @@ import org.springframework.data.domain.Sort;
|
|||||||
*
|
*
|
||||||
* @author Rizwan Idrees
|
* @author Rizwan Idrees
|
||||||
* @author Mohsin Husen
|
* @author Mohsin Husen
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
*/
|
*/
|
||||||
public class StringQuery extends BaseQuery {
|
public class StringQuery extends BaseQuery {
|
||||||
|
|
||||||
private String source;
|
private final String source;
|
||||||
|
|
||||||
public StringQuery(String source) {
|
public StringQuery(String source) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
@ -43,6 +44,21 @@ public class StringQuery extends BaseQuery {
|
|||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public StringQuery(StringQueryBuilder builder) {
|
||||||
|
super(builder);
|
||||||
|
this.source = builder.getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public static StringQueryBuilder builder(String source) {
|
||||||
|
return new StringQueryBuilder(source);
|
||||||
|
}
|
||||||
|
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.core.query;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public class StringQueryBuilder extends BaseQueryBuilder<StringQuery, StringQueryBuilder> {
|
||||||
|
|
||||||
|
private final String source;
|
||||||
|
|
||||||
|
public StringQueryBuilder(String source) {
|
||||||
|
|
||||||
|
Assert.notNull(source, "source must not be null");
|
||||||
|
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringQuery build() {
|
||||||
|
return new StringQuery(this);
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,7 @@ public class ReindexRequest {
|
|||||||
// Request body
|
// Request body
|
||||||
private final Source source;
|
private final Source source;
|
||||||
private final Dest dest;
|
private final Dest dest;
|
||||||
@Nullable private final Integer maxDocs;
|
@Nullable private final Long maxDocs;
|
||||||
@Nullable private final Conflicts conflicts;
|
@Nullable private final Conflicts conflicts;
|
||||||
@Nullable private final Script script;
|
@Nullable private final Script script;
|
||||||
|
|
||||||
@ -46,14 +46,14 @@ public class ReindexRequest {
|
|||||||
@Nullable private final Boolean requireAlias;
|
@Nullable private final Boolean requireAlias;
|
||||||
@Nullable private final Boolean refresh;
|
@Nullable private final Boolean refresh;
|
||||||
@Nullable private final String waitForActiveShards;
|
@Nullable private final String waitForActiveShards;
|
||||||
@Nullable private final Integer requestsPerSecond;
|
@Nullable private final Long requestsPerSecond;
|
||||||
@Nullable private final Duration scroll;
|
@Nullable private final Duration scroll;
|
||||||
@Nullable private final Integer slices;
|
@Nullable private final Long slices;
|
||||||
|
|
||||||
private ReindexRequest(Source source, Dest dest, @Nullable Integer maxDocs, @Nullable Conflicts conflicts,
|
private ReindexRequest(Source source, Dest dest, @Nullable Long maxDocs, @Nullable Conflicts conflicts,
|
||||||
@Nullable Script script, @Nullable Duration timeout, @Nullable Boolean requireAlias, @Nullable Boolean refresh,
|
@Nullable Script script, @Nullable Duration timeout, @Nullable Boolean requireAlias, @Nullable Boolean refresh,
|
||||||
@Nullable String waitForActiveShards, @Nullable Integer requestsPerSecond, @Nullable Duration scroll,
|
@Nullable String waitForActiveShards, @Nullable Long requestsPerSecond, @Nullable Duration scroll,
|
||||||
@Nullable Integer slices) {
|
@Nullable Long slices) {
|
||||||
|
|
||||||
Assert.notNull(source, "source must not be null");
|
Assert.notNull(source, "source must not be null");
|
||||||
Assert.notNull(dest, "dest must not be null");
|
Assert.notNull(dest, "dest must not be null");
|
||||||
@ -73,7 +73,7 @@ public class ReindexRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Integer getMaxDocs() {
|
public Long getMaxDocs() {
|
||||||
return maxDocs;
|
return maxDocs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ public class ReindexRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Integer getRequestsPerSecond() {
|
public Long getRequestsPerSecond() {
|
||||||
return requestsPerSecond;
|
return requestsPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ public class ReindexRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Integer getSlices() {
|
public Long getSlices() {
|
||||||
return slices;
|
return slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,16 +275,16 @@ public class ReindexRequest {
|
|||||||
|
|
||||||
private final Source source;
|
private final Source source;
|
||||||
private final Dest dest;
|
private final Dest dest;
|
||||||
@Nullable private Integer maxDocs;
|
@Nullable private Long maxDocs;
|
||||||
@Nullable private Conflicts conflicts;
|
@Nullable private Conflicts conflicts;
|
||||||
@Nullable private Script script;
|
@Nullable private Script script;
|
||||||
@Nullable private Duration timeout;
|
@Nullable private Duration timeout;
|
||||||
@Nullable private Boolean requireAlias;
|
@Nullable private Boolean requireAlias;
|
||||||
@Nullable private Boolean refresh;
|
@Nullable private Boolean refresh;
|
||||||
@Nullable private String waitForActiveShards;
|
@Nullable private String waitForActiveShards;
|
||||||
@Nullable private Integer requestsPerSecond;
|
@Nullable private Long requestsPerSecond;
|
||||||
@Nullable private Duration scroll;
|
@Nullable private Duration scroll;
|
||||||
@Nullable private Integer slices;
|
@Nullable private Long slices;
|
||||||
|
|
||||||
public ReindexRequestBuilder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) {
|
public ReindexRequestBuilder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) {
|
||||||
|
|
||||||
@ -297,7 +297,7 @@ public class ReindexRequest {
|
|||||||
|
|
||||||
// region setter
|
// region setter
|
||||||
|
|
||||||
public ReindexRequestBuilder withMaxDocs(@Nullable Integer maxDocs) {
|
public ReindexRequestBuilder withMaxDocs(@Nullable Long maxDocs) {
|
||||||
this.maxDocs = maxDocs;
|
this.maxDocs = maxDocs;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -377,7 +377,7 @@ public class ReindexRequest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReindexRequestBuilder withRequestsPerSecond(int requestsPerSecond) {
|
public ReindexRequestBuilder withRequestsPerSecond(long requestsPerSecond) {
|
||||||
this.requestsPerSecond = requestsPerSecond;
|
this.requestsPerSecond = requestsPerSecond;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -387,7 +387,7 @@ public class ReindexRequest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReindexRequestBuilder withSlices(int slices) {
|
public ReindexRequestBuilder withSlices(long slices) {
|
||||||
this.slices = slices;
|
this.slices = slices;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.reindex;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,9 +33,10 @@ public class ReindexResponse {
|
|||||||
private final long took;
|
private final long took;
|
||||||
private final boolean timedOut;
|
private final boolean timedOut;
|
||||||
private final long total;
|
private final long total;
|
||||||
|
private final long created;
|
||||||
private final long updated;
|
private final long updated;
|
||||||
private final long deleted;
|
private final long deleted;
|
||||||
private final int batches;
|
private final long batches;
|
||||||
private final long versionConflicts;
|
private final long versionConflicts;
|
||||||
private final long noops;
|
private final long noops;
|
||||||
private final long bulkRetries;
|
private final long bulkRetries;
|
||||||
@ -44,12 +46,13 @@ public class ReindexResponse {
|
|||||||
private final long throttledUntilMillis;
|
private final long throttledUntilMillis;
|
||||||
private final List<Failure> failures;
|
private final List<Failure> failures;
|
||||||
|
|
||||||
private ReindexResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches,
|
private ReindexResponse(long took, boolean timedOut, long total, long created, long updated, long deleted,
|
||||||
long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis,
|
long batches, long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis,
|
||||||
double requestsPerSecond, long throttledUntilMillis, List<Failure> failures) {
|
double requestsPerSecond, long throttledUntilMillis, List<Failure> failures) {
|
||||||
this.took = took;
|
this.took = took;
|
||||||
this.timedOut = timedOut;
|
this.timedOut = timedOut;
|
||||||
this.total = total;
|
this.total = total;
|
||||||
|
this.created = created;
|
||||||
this.updated = updated;
|
this.updated = updated;
|
||||||
this.deleted = deleted;
|
this.deleted = deleted;
|
||||||
this.batches = batches;
|
this.batches = batches;
|
||||||
@ -84,6 +87,13 @@ public class ReindexResponse {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of documents that were successfully created.
|
||||||
|
*/
|
||||||
|
public long getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of documents that were successfully updated.
|
* The number of documents that were successfully updated.
|
||||||
*/
|
*/
|
||||||
@ -101,7 +111,7 @@ public class ReindexResponse {
|
|||||||
/**
|
/**
|
||||||
* The number of scroll responses pulled back by the update by query.
|
* The number of scroll responses pulled back by the update by query.
|
||||||
*/
|
*/
|
||||||
public int getBatches() {
|
public long getBatches() {
|
||||||
return batches;
|
return batches;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,9 +194,11 @@ public class ReindexResponse {
|
|||||||
@Nullable private final Long seqNo;
|
@Nullable private final Long seqNo;
|
||||||
@Nullable private final Long term;
|
@Nullable private final Long term;
|
||||||
@Nullable private final Boolean aborted;
|
@Nullable private final Boolean aborted;
|
||||||
|
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause,
|
private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception cause,
|
||||||
@Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted) {
|
@Nullable Integer status, @Nullable Long seqNo, @Nullable Long term, @Nullable Boolean aborted,
|
||||||
|
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -195,6 +207,7 @@ public class ReindexResponse {
|
|||||||
this.seqNo = seqNo;
|
this.seqNo = seqNo;
|
||||||
this.term = term;
|
this.term = term;
|
||||||
this.aborted = aborted;
|
this.aborted = aborted;
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -237,6 +250,11 @@ public class ReindexResponse {
|
|||||||
return aborted;
|
return aborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ElasticsearchErrorCause getElasticsearchErrorCause() {
|
||||||
|
return elasticsearchErrorCause;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link Failure.FailureBuilder} to build {@link Failure}
|
* Create a new {@link Failure.FailureBuilder} to build {@link Failure}
|
||||||
*
|
*
|
||||||
@ -258,6 +276,7 @@ public class ReindexResponse {
|
|||||||
@Nullable private Long seqNo;
|
@Nullable private Long seqNo;
|
||||||
@Nullable private Long term;
|
@Nullable private Long term;
|
||||||
@Nullable private Boolean aborted;
|
@Nullable private Boolean aborted;
|
||||||
|
@Nullable private ElasticsearchErrorCause elasticsearchErrorCause;
|
||||||
|
|
||||||
private FailureBuilder() {}
|
private FailureBuilder() {}
|
||||||
|
|
||||||
@ -276,7 +295,7 @@ public class ReindexResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Failure.FailureBuilder withCause(Exception cause) {
|
public Failure.FailureBuilder withCause(@Nullable Exception cause) {
|
||||||
this.cause = cause;
|
this.cause = cause;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -301,8 +320,13 @@ public class ReindexResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Failure.FailureBuilder withErrorCause(@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||||
|
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Failure build() {
|
public Failure build() {
|
||||||
return new Failure(index, type, id, cause, status, seqNo, term, aborted);
|
return new Failure(index, type, id, cause, status, seqNo, term, aborted, elasticsearchErrorCause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,9 +335,10 @@ public class ReindexResponse {
|
|||||||
private long took;
|
private long took;
|
||||||
private boolean timedOut;
|
private boolean timedOut;
|
||||||
private long total;
|
private long total;
|
||||||
|
private long created;
|
||||||
private long updated;
|
private long updated;
|
||||||
private long deleted;
|
private long deleted;
|
||||||
private int batches;
|
private long batches;
|
||||||
private long versionConflicts;
|
private long versionConflicts;
|
||||||
private long noops;
|
private long noops;
|
||||||
private long bulkRetries;
|
private long bulkRetries;
|
||||||
@ -340,6 +365,11 @@ public class ReindexResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReindexResponseBuilder withCreated(long created) {
|
||||||
|
this.created = created;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ReindexResponseBuilder withUpdated(long updated) {
|
public ReindexResponseBuilder withUpdated(long updated) {
|
||||||
this.updated = updated;
|
this.updated = updated;
|
||||||
return this;
|
return this;
|
||||||
@ -350,7 +380,7 @@ public class ReindexResponse {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReindexResponseBuilder withBatches(int batches) {
|
public ReindexResponseBuilder withBatches(long batches) {
|
||||||
this.batches = batches;
|
this.batches = batches;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -396,8 +426,8 @@ public class ReindexResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReindexResponse build() {
|
public ReindexResponse build() {
|
||||||
return new ReindexResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries,
|
return new ReindexResponse(took, timedOut, total, created, updated, deleted, batches, versionConflicts, noops,
|
||||||
searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures);
|
bulkRetries, searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.repository.support;
|
package org.springframework.data.elasticsearch.repository.support;
|
||||||
|
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
|
||||||
import static org.springframework.util.CollectionUtils.*;
|
import static org.springframework.util.CollectionUtils.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -29,8 +28,6 @@ import org.springframework.data.domain.PageImpl;
|
|||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
|
|
||||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
|
||||||
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||||
@ -42,7 +39,9 @@ import org.springframework.data.elasticsearch.core.SearchHits;
|
|||||||
import org.springframework.data.elasticsearch.core.SearchPage;
|
import org.springframework.data.elasticsearch.core.SearchPage;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||||
|
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
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.Query;
|
||||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||||
import org.springframework.data.util.StreamUtils;
|
import org.springframework.data.util.StreamUtils;
|
||||||
@ -116,25 +115,34 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Page<T> findAll(Pageable pageable) {
|
public Page<T> findAll(Pageable pageable) {
|
||||||
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withPageable(pageable).build();
|
|
||||||
|
Assert.notNull(pageable, "pageable must not be null");
|
||||||
|
|
||||||
|
Query query = Query.findAll();
|
||||||
|
query.setPageable(pageable);
|
||||||
SearchHits<T> searchHits = execute(operations -> operations.search(query, entityClass, getIndexCoordinates()));
|
SearchHits<T> searchHits = execute(operations -> operations.search(query, entityClass, getIndexCoordinates()));
|
||||||
// noinspection ConstantConditions
|
|
||||||
SearchPage<T> page = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
SearchPage<T> page = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
||||||
|
// noinspection ConstantConditions
|
||||||
return (Page<T>) SearchHitSupport.unwrapSearchHits(page);
|
return (Page<T>) SearchHitSupport.unwrapSearchHits(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Iterable<T> findAll(Sort sort) {
|
public Iterable<T> findAll(Sort sort) {
|
||||||
|
|
||||||
|
Assert.notNull(sort, "sort must not be null");
|
||||||
|
|
||||||
int itemCount = (int) this.count();
|
int itemCount = (int) this.count();
|
||||||
|
|
||||||
if (itemCount == 0) {
|
if (itemCount == 0) {
|
||||||
return new PageImpl<>(Collections.emptyList());
|
return new PageImpl<>(Collections.emptyList());
|
||||||
}
|
}
|
||||||
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
Pageable pageable = PageRequest.of(0, itemCount, sort);
|
||||||
.withPageable(PageRequest.of(0, itemCount, sort)).build();
|
Query query = Query.findAll();
|
||||||
|
query.setPageable(pageable);
|
||||||
List<SearchHit<T>> searchHitList = execute(
|
List<SearchHit<T>> searchHitList = execute(
|
||||||
operations -> operations.search(query, entityClass, getIndexCoordinates()).getSearchHits());
|
operations -> operations.search(query, entityClass, getIndexCoordinates()).getSearchHits());
|
||||||
|
// noinspection ConstantConditions
|
||||||
return (List<T>) SearchHitSupport.unwrapSearchHits(searchHitList);
|
return (List<T>) SearchHitSupport.unwrapSearchHits(searchHitList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,8 +174,8 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long count() {
|
public long count() {
|
||||||
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
|
Query query = Query.findAll();
|
||||||
// noinspection ConstantConditions
|
((BaseQuery) query).setMaxResults(0);
|
||||||
return execute(operations -> operations.count(query, entityClass, getIndexCoordinates()));
|
return execute(operations -> operations.count(query, entityClass, getIndexCoordinates()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,10 +295,9 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
|||||||
@Override
|
@Override
|
||||||
public void deleteAll() {
|
public void deleteAll() {
|
||||||
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||||
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
|
|
||||||
|
|
||||||
executeAndRefresh((OperationsCallback<Void>) operations -> {
|
executeAndRefresh((OperationsCallback<Void>) operations -> {
|
||||||
operations.delete(query, entityClass, indexCoordinates);
|
operations.delete(Query.findAll(), entityClass, indexCoordinates);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
@ -87,6 +88,7 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public Object get(Object key) {
|
public Object get(Object key) {
|
||||||
return delegate.get(key);
|
return delegate.get(key);
|
||||||
}
|
}
|
||||||
@ -150,4 +152,40 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "DefaultStringObjectMap: " + toJson();
|
return "DefaultStringObjectMap: " + toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the object at the given key path (dot separated) or null if no object exists with this path.
|
||||||
|
*
|
||||||
|
* @param path the key path, must not be {@literal null}
|
||||||
|
* @return the found object or null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Object path(String path) {
|
||||||
|
|
||||||
|
Assert.notNull(path, "path must not be null");
|
||||||
|
|
||||||
|
Map<String, Object> current = this;
|
||||||
|
|
||||||
|
String[] segments = path.split("\\.");
|
||||||
|
for (int i = 0; i < segments.length; i++) {
|
||||||
|
String segment = segments[i];
|
||||||
|
|
||||||
|
if (current.containsKey(segment)) {
|
||||||
|
Object currentObject = current.get(segment);
|
||||||
|
|
||||||
|
if (i == segments.length - 1) {
|
||||||
|
return currentObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentObject instanceof Map) {
|
||||||
|
current = (Map<String, Object>) currentObject;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.support;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
public final class ExceptionUtils {
|
||||||
|
private ExceptionUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks if the given throwable is a {@link ConnectException} or has one in its cause chain
|
||||||
|
*
|
||||||
|
* @param throwable the throwable to check
|
||||||
|
* @return true if throwable is caused by a {@link ConnectException}
|
||||||
|
*/
|
||||||
|
public static boolean isCausedByConnectionException(Throwable throwable) {
|
||||||
|
|
||||||
|
Throwable t = throwable;
|
||||||
|
do {
|
||||||
|
|
||||||
|
if (t instanceof ConnectException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
t = t.getCause();
|
||||||
|
} while (t != null);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
|
||||||
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class demonstrating the setup of a JUnit 5 test in Spring Data Elasticsearch that uses the new rest client. The
|
||||||
|
* ContextConfiguration must include the {@link ElasticsearchTemplateConfiguration} class.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@SpringIntegrationTest
|
||||||
|
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
|
||||||
|
@DisplayName("a sample JUnit 5 test with the new rest client")
|
||||||
|
public class JUnit5SampleElasticsearchTemplateTests {
|
||||||
|
|
||||||
|
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("should have an ElasticsearchTemplate")
|
||||||
|
void shouldHaveAElasticsearchTemplate() {
|
||||||
|
assertThat(elasticsearchOperations).isNotNull().isInstanceOf(ElasticsearchTemplate.class);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate;
|
||||||
|
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||||
|
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||||
|
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class demonstrating the setup of a reactive JUnit 5 test in Spring Data Elasticsearch that uses the new rest client.
|
||||||
|
* The ContextConfiguration must include the {@link ReactiveElasticsearchTemplateConfiguration} class.
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@SpringIntegrationTest
|
||||||
|
@ContextConfiguration(classes = { ReactiveElasticsearchTemplateConfiguration.class })
|
||||||
|
@DisplayName("a sample reactive JUnit 5 test with the new rest client")
|
||||||
|
public class JUnit5SampleReactiveELCTests {
|
||||||
|
|
||||||
|
@Autowired private ReactiveElasticsearchOperations elasticsearchOperations;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("should have an ReactiveElasticsearchTemplate")
|
||||||
|
void shouldHaveAElasticsearchTemplate() {
|
||||||
|
assertThat(elasticsearchOperations).isNotNull().isInstanceOf(ReactiveElasticsearchTemplate.class);
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
@SpringIntegrationTest
|
@SpringIntegrationTest
|
||||||
@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class })
|
@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class })
|
||||||
@DisplayName("a sample JUnit 5 test with reactive rest client")
|
@DisplayName("a sample JUnit 5 test with reactive rest client")
|
||||||
public class JUnit5SampleReactiveRestClientBasedTests {
|
public class JUnit5SampleReactiveERHLCTests {
|
||||||
|
|
||||||
@Autowired private ReactiveElasticsearchOperations elasticsearchOperations;
|
@Autowired private ReactiveElasticsearchOperations elasticsearchOperations;
|
||||||
|
|
@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
@SpringIntegrationTest
|
@SpringIntegrationTest
|
||||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||||
@DisplayName("a sample JUnit 5 test with rest client")
|
@DisplayName("a sample JUnit 5 test with rest client")
|
||||||
public class JUnit5SampleRestTemplateBasedTests {
|
public class JUnit5SampleRestTemplateTests {
|
||||||
|
|
||||||
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
@Autowired private ElasticsearchOperations elasticsearchOperations;
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO #1973 remove when the new Elasticsearch client is fully working
|
||||||
|
*
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
*/
|
||||||
|
public interface NewElasticsearchClientDevelopment {
|
||||||
|
|
||||||
|
boolean forceEnable = false;
|
||||||
|
|
||||||
|
default boolean usesNewElasticsearchClient() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean newElasticsearchClient() {
|
||||||
|
return !forceEnable && usesNewElasticsearchClient();
|
||||||
|
}
|
||||||
|
}
|
@ -28,11 +28,12 @@ public class BlockHoundIntegrationCustomizer implements BlockHoundIntegration {
|
|||||||
public void applyTo(BlockHound.Builder builder) {
|
public void applyTo(BlockHound.Builder builder) {
|
||||||
// Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses
|
// Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses
|
||||||
builder //
|
builder //
|
||||||
|
.allowBlockingCallsInside("org.elasticsearch.Build", "<clinit>") //
|
||||||
.allowBlockingCallsInside("org.elasticsearch.common.xcontent.XContentBuilder", "<clinit>") // pre 7.16
|
.allowBlockingCallsInside("org.elasticsearch.common.xcontent.XContentBuilder", "<clinit>") // pre 7.16
|
||||||
.allowBlockingCallsInside("org.elasticsearch.common.XContentBuilder", "<clinit>") // from 7.16 on
|
.allowBlockingCallsInside("org.elasticsearch.common.XContentBuilder", "<clinit>") // from 7.16 on
|
||||||
.allowBlockingCallsInside("org.elasticsearch.xcontent.json.JsonXContent", "contentBuilder") // from 7.16 on
|
.allowBlockingCallsInside("org.elasticsearch.xcontent.json.JsonXContent", "contentBuilder") // from 7.16 on
|
||||||
.allowBlockingCallsInside("org.elasticsearch.Build", "<clinit>");
|
.allowBlockingCallsInside("jakarta.json.spi.JsonProvider", "provider") //
|
||||||
|
;
|
||||||
builder.blockingMethodCallback(it -> {
|
builder.blockingMethodCallback(it -> {
|
||||||
new Error(it.toString()).printStackTrace();
|
new Error(it.toString()).printStackTrace();
|
||||||
throw new BlockingOperationError(it);
|
throw new BlockingOperationError(it);
|
||||||
|
@ -21,6 +21,8 @@ import static io.specto.hoverfly.junit.dsl.HoverflyDsl.*;
|
|||||||
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
import static io.specto.hoverfly.junit.verification.HoverflyVerifications.*;
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||||
import io.specto.hoverfly.junit.core.Hoverfly;
|
import io.specto.hoverfly.junit.core.Hoverfly;
|
||||||
import io.specto.hoverfly.junit5.HoverflyExtension;
|
import io.specto.hoverfly.junit5.HoverflyExtension;
|
||||||
import io.specto.hoverfly.junit5.api.HoverflyCapture;
|
import io.specto.hoverfly.junit5.api.HoverflyCapture;
|
||||||
@ -40,7 +42,8 @@ import org.junit.jupiter.api.DisplayName;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients;
|
||||||
|
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient;
|
||||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
|||||||
@ExtendWith(HoverflyExtension.class)
|
@ExtendWith(HoverflyExtension.class)
|
||||||
public class RestClientsTest {
|
public class RestClientsTest {
|
||||||
|
|
||||||
@ParameterizedTest // DATAES-700
|
@ParameterizedTest(name = "{0}") // DATAES-700
|
||||||
@MethodSource("clientUnderTestFactorySource")
|
@MethodSource("clientUnderTestFactorySource")
|
||||||
@DisplayName("should use configured proxy")
|
@DisplayName("should use configured proxy")
|
||||||
void shouldUseConfiguredProxy(ClientUnderTestFactory clientUnderTestFactory, Hoverfly hoverfly) throws IOException {
|
void shouldUseConfiguredProxy(ClientUnderTestFactory clientUnderTestFactory, Hoverfly hoverfly) throws IOException {
|
||||||
@ -87,10 +90,11 @@ public class RestClientsTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest // DATAES-801, DATAES-588
|
@ParameterizedTest(name = "{0}") // DATAES-801, DATAES-588
|
||||||
@MethodSource("clientUnderTestFactorySource")
|
@MethodSource("clientUnderTestFactorySource")
|
||||||
@DisplayName("should configure client and set all required headers")
|
@DisplayName("should configure client and set all required headers")
|
||||||
void shouldConfigureClientAndSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
void shouldConfigureClientAndSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||||
|
|
||||||
wireMockServer(server -> {
|
wireMockServer(server -> {
|
||||||
|
|
||||||
HttpHeaders defaultHeaders = new HttpHeaders();
|
HttpHeaders defaultHeaders = new HttpHeaders();
|
||||||
@ -112,17 +116,29 @@ public class RestClientsTest {
|
|||||||
return httpHeaders;
|
return httpHeaders;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (clientUnderTestFactory instanceof RestClientUnderTestFactory) {
|
if (clientUnderTestFactory instanceof ERHLCUnderTestFactory) {
|
||||||
configurationBuilder
|
configurationBuilder
|
||||||
.withClientConfigurer(RestClients.RestClientConfigurationCallback.from(httpClientBuilder -> {
|
.withClientConfigurer(RestClients.RestClientConfigurationCallback.from(httpClientBuilder -> {
|
||||||
clientConfigurerCount.incrementAndGet();
|
clientConfigurerCount.incrementAndGet();
|
||||||
return httpClientBuilder;
|
return httpClientBuilder;
|
||||||
}));
|
}));
|
||||||
} else if (clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory) {
|
} else if (clientUnderTestFactory instanceof ReactiveERHLCUnderTestFactory) {
|
||||||
configurationBuilder.withClientConfigurer(ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
|
configurationBuilder.withClientConfigurer(ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
|
||||||
clientConfigurerCount.incrementAndGet();
|
clientConfigurerCount.incrementAndGet();
|
||||||
return webClient;
|
return webClient;
|
||||||
}));
|
}));
|
||||||
|
} else if (clientUnderTestFactory instanceof ELCUnderTestFactory) {
|
||||||
|
configurationBuilder.withClientConfigurer(
|
||||||
|
ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(httpClientBuilder -> {
|
||||||
|
clientConfigurerCount.incrementAndGet();
|
||||||
|
return httpClientBuilder;
|
||||||
|
}));
|
||||||
|
} else if (clientUnderTestFactory instanceof ReactiveELCUnderTestFactory) {
|
||||||
|
configurationBuilder
|
||||||
|
.withClientConfigurer(ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(webClient -> {
|
||||||
|
clientConfigurerCount.incrementAndGet();
|
||||||
|
return webClient;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientConfiguration clientConfiguration = configurationBuilder.build();
|
ClientConfiguration clientConfiguration = configurationBuilder.build();
|
||||||
@ -130,7 +146,8 @@ public class RestClientsTest {
|
|||||||
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
|
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
|
||||||
|
|
||||||
// do several calls to check that the headerSupplier provided values are set
|
// do several calls to check that the headerSupplier provided values are set
|
||||||
for (int i = 1; i <= 3; i++) {
|
int startValue = clientUnderTest.usesInitialRequest() ? 2 : 1;
|
||||||
|
for (int i = startValue; i <= startValue + 2; i++) {
|
||||||
clientUnderTest.ping();
|
clientUnderTest.ping();
|
||||||
|
|
||||||
verify(headRequestedFor(urlEqualTo("/")) //
|
verify(headRequestedFor(urlEqualTo("/")) //
|
||||||
@ -140,7 +157,7 @@ public class RestClientsTest {
|
|||||||
.withHeader("def2", new EqualToPattern("def2-1")) //
|
.withHeader("def2", new EqualToPattern("def2-1")) //
|
||||||
.withHeader("supplied", new EqualToPattern("val0")) //
|
.withHeader("supplied", new EqualToPattern("val0")) //
|
||||||
// on the first call Elasticsearch does the version check and thus already increments the counter
|
// on the first call Elasticsearch does the version check and thus already increments the counter
|
||||||
.withHeader("supplied", new EqualToPattern("val" + (i + 1))) //
|
.withHeader("supplied", new EqualToPattern("val" + (i))) //
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +172,8 @@ public class RestClientsTest {
|
|||||||
|
|
||||||
wireMockServer(server -> {
|
wireMockServer(server -> {
|
||||||
|
|
||||||
stubFor(put(urlMatching("^/index/_doc/42(\\?.*)$?")) //
|
String urlPattern = "^/index/_doc/42(\\?.*)?$";
|
||||||
|
stubFor(put(urlMatching(urlPattern)) //
|
||||||
.willReturn(jsonResponse("{\n" + //
|
.willReturn(jsonResponse("{\n" + //
|
||||||
" \"_id\": \"42\",\n" + //
|
" \"_id\": \"42\",\n" + //
|
||||||
" \"_index\": \"test\",\n" + //
|
" \"_index\": \"test\",\n" + //
|
||||||
@ -196,9 +214,9 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
clientUnderTest.save(new Foo("42"));
|
clientUnderTest.index(new Foo("42"));
|
||||||
|
|
||||||
verify(putRequestedFor(urlMatching("^/index/_doc/42(\\?.*)$?")) //
|
verify(putRequestedFor(urlMatching(urlPattern)) //
|
||||||
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
.withHeader("Accept", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||||
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
.withHeader("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||||
);
|
);
|
||||||
@ -286,7 +304,15 @@ public class RestClientsTest {
|
|||||||
*/
|
*/
|
||||||
boolean ping() throws Exception;
|
boolean ping() throws Exception;
|
||||||
|
|
||||||
<T> void save(T entity) throws IOException;
|
/**
|
||||||
|
* @return true if the client send an initial request for discovery, version check or similar.
|
||||||
|
*/
|
||||||
|
boolean usesInitialRequest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save an entity using an index request
|
||||||
|
*/
|
||||||
|
<T> void index(T entity) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,9 +330,9 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ClientUnderTestFactory} implementation for the Standard {@link RestHighLevelClient}.
|
* {@link ClientUnderTestFactory} implementation for the old {@link RestHighLevelClient}.
|
||||||
*/
|
*/
|
||||||
static class RestClientUnderTestFactory extends ClientUnderTestFactory {
|
static class ERHLCUnderTestFactory extends ClientUnderTestFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getDisplayName() {
|
protected String getDisplayName() {
|
||||||
@ -323,7 +349,12 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void save(T entity) throws IOException {
|
public boolean usesInitialRequest() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void index(T entity) throws IOException {
|
||||||
IndexRequest indexRequest = new IndexRequest("index");
|
IndexRequest indexRequest = new IndexRequest("index");
|
||||||
indexRequest.id("42");
|
indexRequest.id("42");
|
||||||
indexRequest.source(entity, XContentType.JSON);
|
indexRequest.source(entity, XContentType.JSON);
|
||||||
@ -335,35 +366,108 @@ public class RestClientsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}.
|
* {@link ClientUnderTestFactory} implementation for the {@link co.elastic.clients.elasticsearch.ElasticsearchClient}.
|
||||||
*/
|
*/
|
||||||
static class ReactiveElasticsearchClientUnderTestFactory extends ClientUnderTestFactory {
|
static class ELCUnderTestFactory extends ClientUnderTestFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getDisplayName() {
|
protected String getDisplayName() {
|
||||||
return "ReactiveElasticsearchClient";
|
return "ElasticsearchClient";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||||
ReactiveElasticsearchClient client = ReactiveRestClients.create(clientConfiguration);
|
|
||||||
|
ElasticsearchClient client = ElasticsearchClients.createImperative(clientConfiguration);
|
||||||
return new ClientUnderTest() {
|
return new ClientUnderTest() {
|
||||||
@Override
|
@Override
|
||||||
public boolean ping() throws Exception {
|
public boolean ping() throws Exception {
|
||||||
return client.ping().block();
|
return client.ping().value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> void save(T entity) throws IOException {
|
public boolean usesInitialRequest() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void index(T entity) throws IOException {
|
||||||
|
client.index(ir -> ir.index("index").id("42").document(entity));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ClientUnderTestFactory} implementation for the
|
||||||
|
* {@link org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient}.
|
||||||
|
*/
|
||||||
|
static class ReactiveERHLCUnderTestFactory extends ClientUnderTestFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDisplayName() {
|
||||||
|
return "ReactiveElasticsearchClient (RHLC based)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||||
|
org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient client = ReactiveRestClients
|
||||||
|
.create(clientConfiguration);
|
||||||
|
return new ClientUnderTest() {
|
||||||
|
@Override
|
||||||
|
public boolean ping() throws Exception {
|
||||||
|
return Boolean.TRUE.equals(client.ping().block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean usesInitialRequest() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void index(T entity) throws IOException {
|
||||||
IndexRequest indexRequest = new IndexRequest("index");
|
IndexRequest indexRequest = new IndexRequest("index");
|
||||||
indexRequest.id("42");
|
indexRequest.id("42");
|
||||||
indexRequest.source("{}", XContentType.JSON);
|
indexRequest.source(entity, XContentType.JSON);
|
||||||
client.index(indexRequest).block();
|
client.index(indexRequest).block();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ClientUnderTestFactory} implementation for the {@link ReactiveElasticsearchClient}.
|
||||||
|
*/
|
||||||
|
static class ReactiveELCUnderTestFactory extends ClientUnderTestFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDisplayName() {
|
||||||
|
return "ReactiveElasticsearchClient (ELC based)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||||
|
|
||||||
|
ReactiveElasticsearchClient client = ElasticsearchClients.createReactive(clientConfiguration);
|
||||||
|
return new ClientUnderTest() {
|
||||||
|
@Override
|
||||||
|
public boolean ping() throws Exception {
|
||||||
|
return Boolean.TRUE.equals(client.ping().map(BooleanResponse::value).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean usesInitialRequest() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void index(T entity) throws IOException {
|
||||||
|
client.index(ir -> ir.index("index").id("42").document(entity)).block();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the factories to use in the parameterized tests
|
* Provides the factories to use in the parameterized tests
|
||||||
*
|
*
|
||||||
@ -371,8 +475,9 @@ public class RestClientsTest {
|
|||||||
*/
|
*/
|
||||||
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
||||||
return Stream.of( //
|
return Stream.of( //
|
||||||
new RestClientUnderTestFactory(), //
|
new ERHLCUnderTestFactory(), //
|
||||||
new ReactiveElasticsearchClientUnderTestFactory() //
|
new ReactiveERHLCUnderTestFactory(), //
|
||||||
);
|
new ELCUnderTestFactory(), //
|
||||||
|
new ReactiveELCUnderTestFactory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.data.elasticsearch.client.elc;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import co.elastic.clients.json.JsonpMapper;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Peter-Josef Meisch
|
||||||
|
* @since 4.4
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AutoCloseableElasticsearchClientTest {
|
||||||
|
|
||||||
|
@Mock private RestClient restClient;
|
||||||
|
@Mock private JsonpMapper jsonMapper;
|
||||||
|
|
||||||
|
@Test // #1973
|
||||||
|
@DisplayName("should close the RestClient")
|
||||||
|
void shouldCloseTheRestClient() throws Exception {
|
||||||
|
|
||||||
|
ElasticsearchTransport transport = new RestClientTransport(restClient, jsonMapper);
|
||||||
|
// noinspection EmptyTryBlock
|
||||||
|
try (AutoCloseableElasticsearchClient ignored = new AutoCloseableElasticsearchClient(transport)) {}
|
||||||
|
|
||||||
|
verify(restClient).close();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user