mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-05-02 12:14:53 +00:00
parent
4e39fb30bc
commit
4eb8f08ad8
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,3 +21,6 @@ target
|
||||
*.iws
|
||||
.idea
|
||||
/.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.
|
||||
*
|
||||
@ -20,98 +21,95 @@ import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* 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/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* 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/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the default
|
||||
* one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
/**
|
||||
* 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";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using transport directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if (mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if (mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if (!outputFile.getParentFile().exists()) {
|
||||
if (!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println("- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,3 +5,7 @@ You find the contribution guidelines for Spring Data projects https://github.com
|
||||
== 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.
|
||||
|
||||
== 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
|
||||
|
||||
# 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() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
|
28
pom.xml
28
pom.xml
@ -19,6 +19,7 @@
|
||||
|
||||
<properties>
|
||||
<elasticsearch>7.17.1</elasticsearch>
|
||||
<elasticsearch-java>7.17.1</elasticsearch-java>
|
||||
<log4j>2.17.1</log4j>
|
||||
<netty>4.1.65.Final</netty>
|
||||
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
|
||||
@ -134,7 +135,7 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Elasticsearch -->
|
||||
<!-- Elasticsearch RestHighLevelClient, will be removed probably in SDE 5 -->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
@ -147,6 +148,30 @@
|
||||
</exclusions>
|
||||
</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 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@ -480,6 +505,7 @@
|
||||
<id>local-maven-repo</id>
|
||||
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
|
||||
</repository>
|
||||
|
||||
</repositories>
|
||||
|
||||
<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]]
|
||||
== 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]]
|
||||
== Breaking Changes
|
||||
|
||||
=== 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).
|
||||
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`.
|
||||
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 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.
|
||||
|
||||
[[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.2-4.3.adoc[]
|
||||
|
||||
include::elasticsearch-migration-guide-4.3-4.4.adoc[]
|
||||
: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;
|
||||
|
||||
import org.springframework.dao.UncategorizedDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
@ -23,11 +24,48 @@ import org.springframework.dao.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) {
|
||||
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);
|
||||
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
|
||||
*/
|
||||
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 Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
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 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.
|
||||
*
|
||||
@ -76,8 +94,28 @@ public abstract class ClientLogger {
|
||||
Supplier<Object> body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%sRequest body: %s", logId,
|
||||
method.toUpperCase(), endpoint, parameters, lineSeparator, body.get()));
|
||||
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s%nRequest body: %s", logId,
|
||||
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.
|
||||
*
|
||||
@ -104,8 +156,24 @@ public abstract class ClientLogger {
|
||||
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(
|
||||
String.format("[%s] Received response: %s%sResponse body: %s", logId, statusCode, lineSeparator, body));
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, 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");
|
||||
* 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
|
||||
* 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.test.context.ContextConfiguration;
|
||||
import org.springframework.dao.UncategorizedDataAccessException;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||
public class RoutingRestTemplateIntegrationTests extends RoutingIntegrationTests {}
|
||||
public class CriteriaQueryException extends UncategorizedDataAccessException {
|
||||
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;
|
||||
|
||||
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.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
@ -120,7 +106,6 @@ import org.springframework.data.util.Lazy;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@ -229,7 +214,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
Assert.notNull(clientConfiguration, "ClientConfiguration 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(),
|
||||
clientConfiguration.getEndpoints().toArray(new InetSocketAddress[0]));
|
||||
@ -241,83 +226,6 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
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) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @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);
|
||||
|
||||
|
@ -15,12 +15,30 @@
|
||||
*/
|
||||
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.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
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.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@ -152,4 +170,96 @@ public interface WebClientProvider {
|
||||
* @since 4.3
|
||||
*/
|
||||
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.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
|
||||
@ -77,7 +78,6 @@ import org.springframework.util.Assert;
|
||||
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
protected ElasticsearchConverter elasticsearchConverter;
|
||||
protected RequestFactory requestFactory;
|
||||
protected EntityOperations entityOperations;
|
||||
@Nullable protected EntityCallbacks entityCallbacks;
|
||||
@Nullable protected RefreshPolicy refreshPolicy;
|
||||
@ -96,8 +96,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
this.entityOperations = new EntityOperations(mappingContext);
|
||||
this.routingResolver = new DefaultRoutingResolver(mappingContext);
|
||||
|
||||
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
||||
|
||||
// initialize the VersionInfo class in the initialization phase
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
VersionInfo.versionProperties();
|
||||
@ -429,7 +427,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
@ -455,13 +453,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return elasticsearchConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.0
|
||||
*/
|
||||
public RequestFactory getRequestFactory() {
|
||||
return requestFactory;
|
||||
}
|
||||
|
||||
protected static String[] toArray(List<String> values) {
|
||||
String[] valuesAsArray = new String[values.size()];
|
||||
return values.toArray(valuesAsArray);
|
||||
@ -551,9 +542,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Nullable
|
||||
private SeqNoPrimaryTerm getEntitySeqNoPrimaryTerm(Object entity) {
|
||||
|
||||
EntityOperations.AdaptibleEntity<Object> adaptibleEntity = entityOperations.forEntity(entity,
|
||||
EntityOperations.AdaptableEntity<Object> adaptableEntity = entityOperations.forEntity(entity,
|
||||
elasticsearchConverter.getConversionService(), routingResolver);
|
||||
return adaptibleEntity.hasSeqNoPrimaryTerm() ? adaptibleEntity.getSeqNoPrimaryTerm() : null;
|
||||
return adaptableEntity.hasSeqNoPrimaryTerm() ? adaptableEntity.getSeqNoPrimaryTerm() : null;
|
||||
}
|
||||
|
||||
private <T> IndexQuery getIndexQuery(T entity) {
|
||||
@ -588,6 +579,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
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
|
||||
*
|
||||
|
@ -45,8 +45,6 @@ import org.springframework.util.Assert;
|
||||
public abstract class AbstractIndexTemplate implements IndexOperations {
|
||||
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
protected final RequestFactory requestFactory;
|
||||
|
||||
@Nullable protected final Class<?> boundClass;
|
||||
@Nullable private final IndexCoordinates boundIndex;
|
||||
|
||||
@ -55,7 +53,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
||||
Assert.notNull(boundClass, "boundClass may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
this.boundClass = boundClass;
|
||||
this.boundIndex = null;
|
||||
}
|
||||
@ -65,7 +62,6 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
||||
Assert.notNull(boundIndex, "boundIndex may not be null");
|
||||
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
this.boundClass = null;
|
||||
this.boundIndex = boundIndex;
|
||||
}
|
||||
@ -195,12 +191,9 @@ public abstract class AbstractIndexTemplate implements IndexOperations {
|
||||
|
||||
@Override
|
||||
public Document createMapping(Class<?> clazz) {
|
||||
return buildMapping(clazz);
|
||||
}
|
||||
|
||||
protected Document buildMapping(Class<?> clazz) {
|
||||
|
||||
// load mapping specified in Mapping annotation if present
|
||||
// noinspection DuplicatedCode
|
||||
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||
|
||||
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 James Bodkin
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
class CriteriaQueryProcessor {
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -21,7 +21,6 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
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.convert.ElasticsearchConverter;
|
||||
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.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
@ -118,13 +117,16 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
private final RestHighLevelClient client;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
protected RequestFactory requestFactory;
|
||||
|
||||
// region Initialization
|
||||
// region _initialization
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client) {
|
||||
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
||||
|
||||
}
|
||||
|
||||
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
|
||||
@ -134,12 +136,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
|
||||
this.client = client;
|
||||
requestFactory = new RequestFactory(this.elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
// region IndexOperations
|
||||
@ -282,22 +295,23 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@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(
|
||||
client -> client.reindex(reindexRequest, RequestOptions.DEFAULT));
|
||||
client -> client.reindex(reindexRequestES, RequestOptions.DEFAULT));
|
||||
return ResponseConverter.reindexResponseOf(bulkByScrollResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String submitReindex(ReindexRequest postReindexRequest) {
|
||||
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
|
||||
public String submitReindex(ReindexRequest reindexRequest) {
|
||||
|
||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
|
||||
return execute(client -> client.submitReindexTask(reindexRequest, RequestOptions.DEFAULT).getTask());
|
||||
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||
|
||||
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,
|
||||
@ -387,7 +401,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, 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) {
|
||||
@ -411,12 +425,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||
index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> SearchScrollHits<T> searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis,
|
||||
Class<T> clazz, IndexCoordinates index) {
|
||||
public <T> SearchScrollHits<T> searchScrollContinue(String scrollId, long scrollTimeInMillis, Class<T> clazz,
|
||||
IndexCoordinates index) {
|
||||
|
||||
SearchScrollRequest request = new SearchScrollRequest(scrollId);
|
||||
request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
|
||||
@ -426,7 +440,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
|
||||
index);
|
||||
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -459,7 +473,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
|
||||
List<SearchHits<T>> res = new ArrayList<>(queries.size());
|
||||
for (int i = 0; i < queries.size(); i++) {
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(items[i].getResponse(), getEntityCreator(documentCallback))));
|
||||
res.add(callback
|
||||
.doWith(SearchDocumentResponseBuilder.from(items[i].getResponse(), getEntityCreator(documentCallback))));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -492,7 +507,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
index);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -525,7 +540,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
index);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -537,11 +552,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return items;
|
||||
}
|
||||
|
||||
private <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
|
||||
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region ClientCallback
|
||||
/**
|
||||
* 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 conversionService 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" })
|
||||
public <T> AdaptibleEntity<T> forEntity(T entity, ConversionService conversionService,
|
||||
public <T> AdaptableEntity<T> forEntity(T entity, ConversionService conversionService,
|
||||
RoutingResolver routingResolver) {
|
||||
|
||||
Assert.notNull(entity, "Bean must not be null!");
|
||||
@ -91,7 +91,7 @@ public class EntityOperations {
|
||||
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 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
|
||||
@ -267,7 +267,7 @@ public class EntityOperations {
|
||||
* @author Christoph Strobl
|
||||
* @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) {
|
||||
|
||||
@ -289,7 +289,7 @@ public class EntityOperations {
|
||||
|
||||
/*
|
||||
* (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
|
||||
@Override
|
||||
@ -302,7 +302,7 @@ public class EntityOperations {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#initializeVersionProperty()
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#initializeVersionProperty()
|
||||
*/
|
||||
@Override
|
||||
public T initializeVersionProperty() {
|
||||
@ -311,7 +311,7 @@ public class EntityOperations {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getVersion()
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#getVersion()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
@ -331,7 +331,7 @@ public class EntityOperations {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#incrementVersion()
|
||||
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptableEntity#incrementVersion()
|
||||
*/
|
||||
@Override
|
||||
public T incrementVersion() {
|
||||
@ -476,7 +476,7 @@ public class EntityOperations {
|
||||
* @param <T>
|
||||
* @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 ConvertingPropertyAccessor<T> propertyAccessor;
|
||||
@ -484,7 +484,7 @@ public class EntityOperations {
|
||||
private final ConversionService conversionService;
|
||||
private final RoutingResolver routingResolver;
|
||||
|
||||
private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity<?> entity,
|
||||
private AdaptableMappedEntity(T bean, ElasticsearchPersistentEntity<?> entity,
|
||||
IdentifierAccessor identifierAccessor, ConvertingPropertyAccessor<T> propertyAccessor,
|
||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||
|
||||
@ -497,7 +497,7 @@ public class EntityOperations {
|
||||
this.routingResolver = routingResolver;
|
||||
}
|
||||
|
||||
static <T> AdaptibleEntity<T> of(T bean,
|
||||
static <T> AdaptableEntity<T> of(T bean,
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context,
|
||||
ConversionService conversionService, RoutingResolver routingResolver) {
|
||||
|
||||
@ -505,7 +505,7 @@ public class EntityOperations {
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(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);
|
||||
}
|
||||
|
||||
|
@ -228,8 +228,7 @@ public interface IndexOperations {
|
||||
boolean putTemplate(PutTemplateRequest putTemplateRequest);
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* gets an index template using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @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
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* gets an index template using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param getTemplateRequest the request parameters
|
||||
* @return TemplateData, {@literal null} if no template with the given name exists.
|
||||
@ -252,8 +250,7 @@ public interface IndexOperations {
|
||||
TemplateData getTemplate(GetTemplateRequest getTemplateRequest);
|
||||
|
||||
/**
|
||||
* check if an index template exists using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* check if an index template exists using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @return {@literal true} if the index exists
|
||||
@ -264,8 +261,7 @@ public interface IndexOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* check if an index template exists using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* check if an index template exists using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param existsTemplateRequest the request parameters
|
||||
* @return {@literal true} if the index exists
|
||||
|
@ -15,12 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Response object for items returned from multiget requests, encapsulating the returned data and potential error
|
||||
* information.
|
||||
*
|
||||
*
|
||||
* @param <T> the entity type
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
@ -61,16 +62,20 @@ public class MultiGetItem<T> {
|
||||
@Nullable private final String type;
|
||||
@Nullable private final String id;
|
||||
@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.type = type;
|
||||
this.id = id;
|
||||
this.exception = exception;
|
||||
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||
}
|
||||
|
||||
public static Failure of(String index, String type, String id, Exception exception) {
|
||||
return new Failure(index, type, id, exception);
|
||||
public static Failure of(String index, @Nullable String type, String id, @Nullable Exception exception,
|
||||
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||
return new Failure(index, type, id, exception, elasticsearchErrorCause);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -92,5 +97,10 @@ public class MultiGetItem<T> {
|
||||
public Exception getException() {
|
||||
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 <T> the type the Publisher emits
|
||||
* @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);
|
||||
|
||||
/**
|
||||
@ -126,7 +128,7 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati
|
||||
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
|
||||
* {@link ReactiveElasticsearchClient}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param <T> result type of the callback
|
||||
* @author Christoph Strobl
|
||||
* @since 3.2
|
||||
*/
|
||||
|
@ -23,10 +23,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
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.elasticsearch.Version;
|
||||
import org.elasticsearch.action.DocWriteResponse;
|
||||
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.search.suggest.SuggestBuilder;
|
||||
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.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.DocumentAdapters;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.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.document.SearchDocumentResponseBuilder;
|
||||
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.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
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.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -109,98 +84,37 @@ import org.springframework.util.Assert;
|
||||
* @author Sijia Liu
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
private static final Log QUERY_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.core.QUERY");
|
||||
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
|
||||
|
||||
private final ReactiveElasticsearchClient client;
|
||||
private final ElasticsearchConverter converter;
|
||||
private final SimpleElasticsearchMappingContext mappingContext;
|
||||
private final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
private final EntityOperations operations;
|
||||
protected RequestFactory requestFactory;
|
||||
|
||||
private @Nullable RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
private @Nullable IndicesOptions indicesOptions = IndicesOptions.strictExpandOpenAndForbidClosedIgnoreThrottled();
|
||||
|
||||
private @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||
|
||||
private RoutingResolver routingResolver;
|
||||
|
||||
// region Initialization
|
||||
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(converter, "converter must not be null");
|
||||
|
||||
this.client = client;
|
||||
this.converter = converter;
|
||||
|
||||
this.mappingContext = (SimpleElasticsearchMappingContext) converter.getMappingContext();
|
||||
this.routingResolver = new DefaultRoutingResolver(this.mappingContext);
|
||||
|
||||
this.exceptionTranslator = new ElasticsearchExceptionTranslator();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
this.requestFactory = new RequestFactory(converter);
|
||||
|
||||
// initialize the VersionInfo class in the initialization phase
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
VersionInfo.versionProperties();
|
||||
this.requestFactory = new RequestFactory(this.converter);
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchTemplate copy() {
|
||||
protected ReactiveElasticsearchTemplate doCopy() {
|
||||
|
||||
ReactiveElasticsearchTemplate copy = new ReactiveElasticsearchTemplate(client, converter);
|
||||
copy.setRefreshPolicy(refreshPolicy);
|
||||
copy.setIndicesOptions(indicesOptions);
|
||||
copy.setEntityCallbacks(entityCallbacks);
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
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}.
|
||||
*
|
||||
@ -210,58 +124,14 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
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
|
||||
|
||||
// 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
|
||||
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 //
|
||||
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||
@ -275,7 +145,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
return doBulkOperation(entities.indexQueries(), BulkOptions.defaultOptions(), index) //
|
||||
.index().flatMap(indexAndResponse -> {
|
||||
.index() //
|
||||
.flatMap(indexAndResponse -> {
|
||||
T savedEntity = entities.entityAt(indexAndResponse.getT1());
|
||||
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
|
||||
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 />
|
||||
* 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)));
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
||||
return client.bulk(bulkRequest) //
|
||||
.onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) //
|
||||
@ -401,17 +228,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
protected Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
return Mono.defer(() -> doExists(requestFactory.getRequest(id, routingResolver.getRouting(), index)));
|
||||
}
|
||||
|
||||
@ -427,54 +244,17 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.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);
|
||||
request = prepareIndexRequest(entity, request);
|
||||
return Mono.just(entity).zipWith(doIndex(request));
|
||||
}
|
||||
|
||||
private IndexQuery getIndexQuery(Object value) {
|
||||
AdaptibleEntity<?> entity = operations.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> get(String id, Class<T> entityType) {
|
||||
return get(id, entityType, getIndexCoordinatesFor(entityType));
|
||||
return Mono.just(entity).zipWith(doIndex(request) //
|
||||
.map(indexResponse -> new IndexResponseMetaData( //
|
||||
indexResponse.getId(), //
|
||||
indexResponse.getSeqNo(), //
|
||||
indexResponse.getPrimaryTerm(), //
|
||||
indexResponse.getVersion() //
|
||||
))); //
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -482,13 +262,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
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);
|
||||
|
||||
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)));
|
||||
return getResult.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -503,51 +281,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (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) {
|
||||
protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
DeleteRequest request = requestFactory.deleteRequest(id, routing, index);
|
||||
@ -633,12 +367,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
|
||||
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
private Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
protected Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
||||
@ -747,16 +476,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
// endregion
|
||||
|
||||
// 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
|
||||
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()));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
protected Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
|
||||
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(() -> {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
@Deprecated
|
||||
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
|
||||
@ -908,17 +578,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
protected Mono<Long> doCount(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
return Mono.defer(() -> {
|
||||
|
||||
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
|
||||
@ -958,7 +618,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
@Override
|
||||
protected Mono<String> getClusterVersion() {
|
||||
try {
|
||||
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
|
||||
* @since 4.3
|
||||
*/
|
||||
@Override
|
||||
protected Mono<String> getVendor() {
|
||||
return Mono.just("Elasticsearch");
|
||||
}
|
||||
@ -1038,6 +699,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
* @return the version of the used client runtime library.
|
||||
* @since 4.3
|
||||
*/
|
||||
@Override
|
||||
protected Mono<String> getRuntimeLibraryVersion() {
|
||||
return Mono.just(Version.CURRENT.toString());
|
||||
}
|
||||
@ -1072,11 +734,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return Flux.defer(() -> callback.doWithClient(getClusterClient())).onErrorMap(this::translateException);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchConverter getElasticsearchConverter() {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
|
||||
return new ReactiveIndexTemplate(this, index);
|
||||
@ -1092,17 +749,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
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.
|
||||
*
|
||||
@ -1157,159 +803,4 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* gets an index template using the legacy Elasticsearch
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* gets an index template using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param templateName the template name
|
||||
* @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
|
||||
* interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java.
|
||||
* gets an index template using the legacy Elasticsearch interface.
|
||||
*
|
||||
* @param getTemplateRequest the request parameters
|
||||
* @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists.
|
||||
|
@ -180,13 +180,14 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
||||
@Override
|
||||
public Mono<Document> createMapping(Class<?> clazz) {
|
||||
|
||||
// noinspection DuplicatedCode
|
||||
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
|
||||
|
||||
if (mappingAnnotation != null) {
|
||||
String mappingPath = mappingAnnotation.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);
|
||||
String settingPath = persistentEntity.settingPath();
|
||||
return hasText(settingPath) //
|
||||
? loadDocument(settingPath, "@Setting") //
|
||||
? ReactiveResourceUtil.loadDocument(settingPath, "@Setting") //
|
||||
.map(Settings::new) //
|
||||
: Mono.just(persistentEntity.getDefaultSettings());
|
||||
}
|
||||
@ -351,23 +352,6 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
||||
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() {
|
||||
if (boundClass == null) {
|
||||
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@ -22,10 +24,13 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
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.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.data.elasticsearch.ResourceFailureException;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@ -36,6 +41,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class ReactiveResourceUtil {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ReactiveResourceUtil.class);
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
private ReactiveResourceUtil() {}
|
||||
}
|
||||
|
@ -23,7 +23,15 @@ import static org.springframework.util.CollectionUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
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 org.elasticsearch.action.DocWriteRequest;
|
||||
@ -41,7 +49,6 @@ import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
import org.elasticsearch.action.index.IndexRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchType;
|
||||
import org.elasticsearch.action.support.ActiveShardCount;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
@ -270,7 +277,7 @@ class RequestFactory {
|
||||
}
|
||||
|
||||
public GetIndexRequest getIndexRequest(IndexCoordinates index) {
|
||||
return new GetIndexRequest(index.getIndexNames());
|
||||
return new GetIndexRequest(index.getIndexNames()).humanReadable(false);
|
||||
}
|
||||
|
||||
public IndicesExistsRequest indicesExistsRequest(IndexCoordinates index) {
|
||||
@ -405,7 +412,7 @@ class RequestFactory {
|
||||
}
|
||||
|
||||
if (reindexRequest.getMaxDocs() != null) {
|
||||
request.setMaxDocs(reindexRequest.getMaxDocs());
|
||||
request.setMaxDocs(Math.toIntExact(reindexRequest.getMaxDocs()));
|
||||
}
|
||||
// region source build
|
||||
final Source source = reindexRequest.getSource();
|
||||
@ -464,12 +471,12 @@ class RequestFactory {
|
||||
|
||||
final org.springframework.data.elasticsearch.annotations.Document.VersionType versionType = dest.getVersionType();
|
||||
if (versionType != null) {
|
||||
request.setDestVersionType(VersionType.fromString(versionType.name().toLowerCase(Locale.ROOT)));
|
||||
request.setDestVersionType(VersionType.fromString(versionType.getEsName()));
|
||||
}
|
||||
|
||||
final IndexQuery.OpType opType = dest.getOpType();
|
||||
if (opType != null) {
|
||||
request.setDestOpType(opType.name().toLowerCase(Locale.ROOT));
|
||||
request.setDestOpType(opType.getEsName());
|
||||
}
|
||||
// endregion
|
||||
|
||||
@ -508,7 +515,7 @@ class RequestFactory {
|
||||
}
|
||||
|
||||
if (reindexRequest.getSlices() != null) {
|
||||
request.setSlices(reindexRequest.getSlices());
|
||||
request.setSlices(Math.toIntExact(reindexRequest.getSlices()));
|
||||
}
|
||||
// endregion
|
||||
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) {
|
||||
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
|
||||
|
||||
|
@ -293,7 +293,7 @@ public class ResponseConverter {
|
||||
|
||||
MultiGetResponse.Failure responseFailure = itemResponse.getFailure();
|
||||
return responseFailure != null ? MultiGetItem.Failure.of(responseFailure.getIndex(), responseFailure.getType(),
|
||||
responseFailure.getId(), responseFailure.getFailure()) : null;
|
||||
responseFailure.getId(), responseFailure.getFailure(), null) : null;
|
||||
}
|
||||
// endregion
|
||||
|
||||
@ -387,8 +387,7 @@ public class ResponseConverter {
|
||||
|
||||
// endregion
|
||||
|
||||
// region postReindexResponse
|
||||
|
||||
// region reindex
|
||||
/**
|
||||
* @since 4.4
|
||||
*/
|
||||
@ -402,6 +401,7 @@ public class ResponseConverter {
|
||||
.withTook(bulkByScrollResponse.getTook().getMillis()) //
|
||||
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
|
||||
.withTotal(bulkByScrollResponse.getTotal()) //
|
||||
.withCreated(bulkByScrollResponse.getCreated()) //
|
||||
.withUpdated(bulkByScrollResponse.getUpdated()) //
|
||||
.withDeleted(bulkByScrollResponse.getDeleted()) //
|
||||
.withBatches(bulkByScrollResponse.getBatches()) //
|
||||
|
@ -66,15 +66,18 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
|
||||
private static final Log LOGGER = LogFactory.getLog(RestIndexTemplate.class);
|
||||
|
||||
private final ElasticsearchRestTemplate restTemplate;
|
||||
protected final RequestFactory requestFactory;
|
||||
|
||||
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
|
||||
super(restTemplate.getElasticsearchConverter(), boundClass);
|
||||
this.restTemplate = restTemplate;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
}
|
||||
|
||||
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
|
||||
super(restTemplate.getElasticsearchConverter(), boundIndex);
|
||||
this.restTemplate = restTemplate;
|
||||
requestFactory = new RequestFactory(elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,7 +119,7 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
|
||||
@Override
|
||||
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);
|
||||
|
||||
|
@ -57,4 +57,18 @@ public class RuntimeField {
|
||||
map.put("script", script);
|
||||
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 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.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
|
||||
|
@ -19,9 +19,10 @@ import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
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.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@ -77,7 +78,9 @@ public interface SearchOperations {
|
||||
* and get the suggest from {@link SearchHits#getSuggest()}
|
||||
*/
|
||||
@Deprecated
|
||||
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
|
||||
default SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
|
||||
throw new InvalidDataAccessApiUsageException("Unsupported operation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a suggest query
|
||||
@ -90,7 +93,9 @@ public interface SearchOperations {
|
||||
* and get the suggest from {@link SearchHits#getSuggest()}
|
||||
*/
|
||||
@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.
|
||||
|
@ -122,13 +122,23 @@ public class ClusterHealth {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClusterHealth{" + "clusterName='" + clusterName + '\'' + ", status='" + status + '\'' + ", numberOfNodes="
|
||||
+ numberOfNodes + ", numberOfDataNodes=" + numberOfDataNodes + ", activeShards=" + activeShards
|
||||
+ ", relocatingShards=" + relocatingShards + ", activePrimaryShards=" + activePrimaryShards
|
||||
+ ", initializingShards=" + initializingShards + ", unassignedShards=" + unassignedShards
|
||||
+ ", activeShardsPercent=" + activeShardsPercent + ", numberOfPendingTasks=" + numberOfPendingTasks
|
||||
+ ", timedOut=" + timedOut + ", numberOfInFlightFetch=" + numberOfInFlightFetch + ", delayedUnassignedShards="
|
||||
+ delayedUnassignedShards + ", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + '}';
|
||||
return "ClusterHealth{" + //
|
||||
"clusterName='" + clusterName + '\'' + //
|
||||
", status='" + status + '\'' + //
|
||||
", numberOfNodes=" + numberOfNodes + //
|
||||
", numberOfDataNodes=" + numberOfDataNodes + //
|
||||
", activeShards=" + activeShards + //
|
||||
", relocatingShards=" + relocatingShards + //
|
||||
", activePrimaryShards=" + activePrimaryShards + //
|
||||
", initializingShards=" + initializingShards + //
|
||||
", unassignedShards=" + unassignedShards + //
|
||||
", activeShardsPercent=" + activeShardsPercent + //
|
||||
", numberOfPendingTasks=" + numberOfPendingTasks + //
|
||||
", timedOut=" + timedOut + //
|
||||
", numberOfInFlightFetch=" + numberOfInFlightFetch + //
|
||||
", delayedUnassignedShards=" + delayedUnassignedShards + //
|
||||
", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + //
|
||||
'}'; //
|
||||
}
|
||||
|
||||
public static ClusterHealthBuilder builder() {
|
||||
@ -160,7 +170,7 @@ public class ClusterHealth {
|
||||
}
|
||||
|
||||
public ClusterHealthBuilder withStatus(String status) {
|
||||
this.status = status;
|
||||
this.status = status.toUpperCase();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -177,8 +178,8 @@ public final class DocumentAdapters {
|
||||
Map<String, SearchHits> sourceInnerHits = source.getInnerHits();
|
||||
|
||||
if (sourceInnerHits != null) {
|
||||
sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name,
|
||||
SearchDocumentResponse.from(searchHits, null, null, null, searchDocument -> null)));
|
||||
sourceInnerHits.forEach((name, searchHits) -> innerHits.put(name, SearchDocumentResponseBuilder.from(searchHits,
|
||||
null, null, null, searchDocument -> CompletableFuture.completedFuture(null))));
|
||||
}
|
||||
|
||||
NestedMetaData nestedMetaData = from(source.getNestedIdentity());
|
||||
|
@ -23,16 +23,16 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* class that holds explanations returned from an Elasticsearch search.
|
||||
*
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class Explanation {
|
||||
private final boolean match;
|
||||
@Nullable private final Boolean match;
|
||||
private final Double value;
|
||||
@Nullable private final String description;
|
||||
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(details, "details must not be null");
|
||||
@ -44,7 +44,7 @@ public class Explanation {
|
||||
}
|
||||
|
||||
public boolean isMatch() {
|
||||
return match;
|
||||
return match != null && match;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
|
||||
* created with the {@link #from(SearchResponse, EntityCreator)} method.
|
||||
* This represents the complete search response from Elasticsearch, including the returned documents.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SearchDocumentResponse {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
|
||||
|
||||
private final long totalHits;
|
||||
private final String totalHitsRelation;
|
||||
private final float maxScore;
|
||||
private final String scrollId;
|
||||
@Nullable private final String scrollId;
|
||||
private final List<SearchDocument> searchDocuments;
|
||||
@Nullable private final AggregationsContainer<?> aggregations;
|
||||
@Nullable private final Suggest suggest;
|
||||
|
||||
private SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, String scrollId,
|
||||
List<SearchDocument> searchDocuments, @Nullable Aggregations aggregations, @Nullable Suggest suggest) {
|
||||
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
|
||||
List<SearchDocument> searchDocuments, @Nullable AggregationsContainer<?> aggregationsContainer,
|
||||
@Nullable Suggest suggest) {
|
||||
this.totalHits = totalHits;
|
||||
this.totalHitsRelation = totalHitsRelation;
|
||||
this.maxScore = maxScore;
|
||||
this.scrollId = scrollId;
|
||||
this.searchDocuments = searchDocuments;
|
||||
this.aggregations = aggregations != null ? new ElasticsearchAggregations(aggregations) : null;
|
||||
this.aggregations = aggregationsContainer;
|
||||
this.suggest = suggest;
|
||||
}
|
||||
|
||||
@ -81,6 +63,7 @@ public class SearchDocumentResponse {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getScrollId() {
|
||||
return scrollId;
|
||||
}
|
||||
@ -99,178 +82,6 @@ public class SearchDocumentResponse {
|
||||
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
|
||||
* 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 {
|
||||
|
||||
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_PROPERTIES = "properties";
|
||||
|
@ -15,9 +15,14 @@
|
||||
*/
|
||||
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.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.
|
||||
@ -47,4 +52,77 @@ public class Settings extends DefaultStringObjectMap<Settings> {
|
||||
public String toString() {
|
||||
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 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
|
||||
@Nullable
|
||||
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.List;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -173,9 +174,11 @@ public class ByQueryResponse {
|
||||
@Nullable private final Long seqNo;
|
||||
@Nullable private final Long term;
|
||||
@Nullable private final Boolean aborted;
|
||||
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||
|
||||
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.type = type;
|
||||
this.id = id;
|
||||
@ -184,6 +187,7 @@ public class ByQueryResponse {
|
||||
this.seqNo = seqNo;
|
||||
this.term = term;
|
||||
this.aborted = aborted;
|
||||
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -247,6 +251,7 @@ public class ByQueryResponse {
|
||||
@Nullable private Long seqNo;
|
||||
@Nullable private Long term;
|
||||
@Nullable private Boolean aborted;
|
||||
@Nullable private ElasticsearchErrorCause elasticsearchErrorCause;
|
||||
|
||||
private FailureBuilder() {}
|
||||
|
||||
@ -290,8 +295,13 @@ public class ByQueryResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FailureBuilder withErrorCause(ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||
return this;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -168,6 +168,16 @@ public class IndexQuery {
|
||||
* @since 4.2
|
||||
*/
|
||||
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;
|
||||
|
||||
public NativeSearchQuery(@Nullable QueryBuilder query) {
|
||||
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@ -99,6 +98,17 @@ public class NativeSearchQuery extends BaseQuery {
|
||||
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
|
||||
public QueryBuilder getQuery() {
|
||||
return query;
|
||||
|
@ -32,7 +32,6 @@ import org.elasticsearch.search.collapse.CollapseBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -52,7 +51,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author vdisk
|
||||
* @author owen.qq
|
||||
*/
|
||||
public class NativeSearchQueryBuilder {
|
||||
public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery, NativeSearchQueryBuilder> {
|
||||
|
||||
@Nullable private QueryBuilder queryBuilder;
|
||||
@Nullable private QueryBuilder filterBuilder;
|
||||
@ -62,21 +61,12 @@ public class NativeSearchQueryBuilder {
|
||||
private final List<PipelineAggregationBuilder> pipelineAggregationBuilders = new ArrayList<>();
|
||||
@Nullable private HighlightBuilder highlightBuilder;
|
||||
@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 private SourceFilter sourceFilter;
|
||||
@Nullable private CollapseBuilder collapseBuilder;
|
||||
@Nullable private List<IndexBoost> indicesBoost = new ArrayList<>();
|
||||
@Nullable private SearchTemplateRequestBuilder searchTemplateBuilder;
|
||||
private float minScore;
|
||||
private boolean trackScores;
|
||||
@Nullable private List<String> ids = new ArrayList<>();
|
||||
@Nullable private String route;
|
||||
@Nullable private SearchType searchType;
|
||||
@Nullable private IndicesOptions indicesOptions;
|
||||
@Nullable private String preference;
|
||||
@Nullable private Integer maxResults;
|
||||
@Nullable private Boolean trackTotalHits;
|
||||
@Nullable private Duration timeout;
|
||||
private final List<RescorerQuery> rescorerQueries = new ArrayList<>();
|
||||
@ -223,24 +213,6 @@ public class NativeSearchQueryBuilder {
|
||||
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) {
|
||||
|
||||
if (this.storedFields == null) {
|
||||
@ -260,39 +232,6 @@ public class NativeSearchQueryBuilder {
|
||||
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) {
|
||||
this.route = route;
|
||||
return this;
|
||||
@ -308,16 +247,6 @@ public class NativeSearchQueryBuilder {
|
||||
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
|
||||
*/
|
||||
@ -356,27 +285,17 @@ public class NativeSearchQueryBuilder {
|
||||
public NativeSearchQuery build() {
|
||||
|
||||
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( //
|
||||
this, //
|
||||
queryBuilder, //
|
||||
filterBuilder, //
|
||||
sortBuilders, //
|
||||
highlightBuilder, //
|
||||
highlightFields.toArray(new HighlightBuilder.Field[highlightFields.size()]));
|
||||
|
||||
nativeSearchQuery.setPageable(pageable);
|
||||
nativeSearchQuery.setTrackScores(trackScores);
|
||||
|
||||
if (fields != null) {
|
||||
nativeSearchQuery.setFields(fields);
|
||||
}
|
||||
|
||||
if (storedFields != null) {
|
||||
nativeSearchQuery.setStoredFields(storedFields);
|
||||
}
|
||||
|
||||
if (sourceFilter != null) {
|
||||
nativeSearchQuery.addSourceFilter(sourceFilter);
|
||||
}
|
||||
|
||||
if (indicesBoost != null) {
|
||||
nativeSearchQuery.setIndicesBoost(indicesBoost);
|
||||
}
|
||||
@ -401,14 +320,6 @@ public class NativeSearchQueryBuilder {
|
||||
nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders);
|
||||
}
|
||||
|
||||
if (minScore > 0) {
|
||||
nativeSearchQuery.setMinScore(minScore);
|
||||
}
|
||||
|
||||
if (ids != null) {
|
||||
nativeSearchQuery.setIds(ids);
|
||||
}
|
||||
|
||||
if (route != null) {
|
||||
nativeSearchQuery.setRoute(route);
|
||||
}
|
||||
@ -421,14 +332,6 @@ public class NativeSearchQueryBuilder {
|
||||
nativeSearchQuery.setIndicesOptions(indicesOptions);
|
||||
}
|
||||
|
||||
if (preference != null) {
|
||||
nativeSearchQuery.setPreference(preference);
|
||||
}
|
||||
|
||||
if (maxResults != null) {
|
||||
nativeSearchQuery.setMaxResults(maxResults);
|
||||
}
|
||||
|
||||
nativeSearchQuery.setTrackTotalHits(trackTotalHits);
|
||||
|
||||
if (timeout != null) {
|
||||
|
@ -339,7 +339,7 @@ public interface Query {
|
||||
void setScrollTime(@Nullable Duration scrollTime);
|
||||
|
||||
/**
|
||||
* @return {@literal true} if scrollTimeMillis is set.
|
||||
* @return {@literal true} if a scroll time is set.
|
||||
* @since 4.0
|
||||
*/
|
||||
default boolean hasScrollTime() {
|
||||
|
@ -23,10 +23,11 @@ import org.springframework.data.domain.Sort;
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class StringQuery extends BaseQuery {
|
||||
|
||||
private String source;
|
||||
private final String source;
|
||||
|
||||
public StringQuery(String source) {
|
||||
this.source = source;
|
||||
@ -43,6 +44,21 @@ public class StringQuery extends BaseQuery {
|
||||
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() {
|
||||
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
|
||||
private final Source source;
|
||||
private final Dest dest;
|
||||
@Nullable private final Integer maxDocs;
|
||||
@Nullable private final Long maxDocs;
|
||||
@Nullable private final Conflicts conflicts;
|
||||
@Nullable private final Script script;
|
||||
|
||||
@ -46,14 +46,14 @@ public class ReindexRequest {
|
||||
@Nullable private final Boolean requireAlias;
|
||||
@Nullable private final Boolean refresh;
|
||||
@Nullable private final String waitForActiveShards;
|
||||
@Nullable private final Integer requestsPerSecond;
|
||||
@Nullable private final Long requestsPerSecond;
|
||||
@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 String waitForActiveShards, @Nullable Integer requestsPerSecond, @Nullable Duration scroll,
|
||||
@Nullable Integer slices) {
|
||||
@Nullable String waitForActiveShards, @Nullable Long requestsPerSecond, @Nullable Duration scroll,
|
||||
@Nullable Long slices) {
|
||||
|
||||
Assert.notNull(source, "source must not be null");
|
||||
Assert.notNull(dest, "dest must not be null");
|
||||
@ -73,7 +73,7 @@ public class ReindexRequest {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getMaxDocs() {
|
||||
public Long getMaxDocs() {
|
||||
return maxDocs;
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ public class ReindexRequest {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getRequestsPerSecond() {
|
||||
public Long getRequestsPerSecond() {
|
||||
return requestsPerSecond;
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ public class ReindexRequest {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getSlices() {
|
||||
public Long getSlices() {
|
||||
return slices;
|
||||
}
|
||||
|
||||
@ -275,16 +275,16 @@ public class ReindexRequest {
|
||||
|
||||
private final Source source;
|
||||
private final Dest dest;
|
||||
@Nullable private Integer maxDocs;
|
||||
@Nullable private Long maxDocs;
|
||||
@Nullable private Conflicts conflicts;
|
||||
@Nullable private Script script;
|
||||
@Nullable private Duration timeout;
|
||||
@Nullable private Boolean requireAlias;
|
||||
@Nullable private Boolean refresh;
|
||||
@Nullable private String waitForActiveShards;
|
||||
@Nullable private Integer requestsPerSecond;
|
||||
@Nullable private Long requestsPerSecond;
|
||||
@Nullable private Duration scroll;
|
||||
@Nullable private Integer slices;
|
||||
@Nullable private Long slices;
|
||||
|
||||
public ReindexRequestBuilder(IndexCoordinates sourceIndex, IndexCoordinates destIndex) {
|
||||
|
||||
@ -297,7 +297,7 @@ public class ReindexRequest {
|
||||
|
||||
// region setter
|
||||
|
||||
public ReindexRequestBuilder withMaxDocs(@Nullable Integer maxDocs) {
|
||||
public ReindexRequestBuilder withMaxDocs(@Nullable Long maxDocs) {
|
||||
this.maxDocs = maxDocs;
|
||||
return this;
|
||||
}
|
||||
@ -377,7 +377,7 @@ public class ReindexRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReindexRequestBuilder withRequestsPerSecond(int requestsPerSecond) {
|
||||
public ReindexRequestBuilder withRequestsPerSecond(long requestsPerSecond) {
|
||||
this.requestsPerSecond = requestsPerSecond;
|
||||
return this;
|
||||
}
|
||||
@ -387,7 +387,7 @@ public class ReindexRequest {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReindexRequestBuilder withSlices(int slices) {
|
||||
public ReindexRequestBuilder withSlices(long slices) {
|
||||
this.slices = slices;
|
||||
return this;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core.reindex;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@ -32,9 +33,10 @@ public class ReindexResponse {
|
||||
private final long took;
|
||||
private final boolean timedOut;
|
||||
private final long total;
|
||||
private final long created;
|
||||
private final long updated;
|
||||
private final long deleted;
|
||||
private final int batches;
|
||||
private final long batches;
|
||||
private final long versionConflicts;
|
||||
private final long noops;
|
||||
private final long bulkRetries;
|
||||
@ -44,12 +46,13 @@ public class ReindexResponse {
|
||||
private final long throttledUntilMillis;
|
||||
private final List<Failure> failures;
|
||||
|
||||
private ReindexResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches,
|
||||
long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis,
|
||||
private ReindexResponse(long took, boolean timedOut, long total, long created, long updated, long deleted,
|
||||
long batches, long versionConflicts, long noops, long bulkRetries, long searchRetries, long throttledMillis,
|
||||
double requestsPerSecond, long throttledUntilMillis, List<Failure> failures) {
|
||||
this.took = took;
|
||||
this.timedOut = timedOut;
|
||||
this.total = total;
|
||||
this.created = created;
|
||||
this.updated = updated;
|
||||
this.deleted = deleted;
|
||||
this.batches = batches;
|
||||
@ -84,6 +87,13 @@ public class ReindexResponse {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of documents that were successfully created.
|
||||
*/
|
||||
public long getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public int getBatches() {
|
||||
public long getBatches() {
|
||||
return batches;
|
||||
}
|
||||
|
||||
@ -184,9 +194,11 @@ public class ReindexResponse {
|
||||
@Nullable private final Long seqNo;
|
||||
@Nullable private final Long term;
|
||||
@Nullable private final Boolean aborted;
|
||||
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
|
||||
|
||||
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.type = type;
|
||||
this.id = id;
|
||||
@ -195,6 +207,7 @@ public class ReindexResponse {
|
||||
this.seqNo = seqNo;
|
||||
this.term = term;
|
||||
this.aborted = aborted;
|
||||
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -237,6 +250,11 @@ public class ReindexResponse {
|
||||
return aborted;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ElasticsearchErrorCause getElasticsearchErrorCause() {
|
||||
return elasticsearchErrorCause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Failure.FailureBuilder} to build {@link Failure}
|
||||
*
|
||||
@ -258,6 +276,7 @@ public class ReindexResponse {
|
||||
@Nullable private Long seqNo;
|
||||
@Nullable private Long term;
|
||||
@Nullable private Boolean aborted;
|
||||
@Nullable private ElasticsearchErrorCause elasticsearchErrorCause;
|
||||
|
||||
private FailureBuilder() {}
|
||||
|
||||
@ -276,7 +295,7 @@ public class ReindexResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Failure.FailureBuilder withCause(Exception cause) {
|
||||
public Failure.FailureBuilder withCause(@Nullable Exception cause) {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
@ -301,8 +320,13 @@ public class ReindexResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Failure.FailureBuilder withErrorCause(@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
|
||||
this.elasticsearchErrorCause = elasticsearchErrorCause;
|
||||
return this;
|
||||
}
|
||||
|
||||
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 boolean timedOut;
|
||||
private long total;
|
||||
private long created;
|
||||
private long updated;
|
||||
private long deleted;
|
||||
private int batches;
|
||||
private long batches;
|
||||
private long versionConflicts;
|
||||
private long noops;
|
||||
private long bulkRetries;
|
||||
@ -340,6 +365,11 @@ public class ReindexResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReindexResponseBuilder withCreated(long created) {
|
||||
this.created = created;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReindexResponseBuilder withUpdated(long updated) {
|
||||
this.updated = updated;
|
||||
return this;
|
||||
@ -350,7 +380,7 @@ public class ReindexResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReindexResponseBuilder withBatches(int batches) {
|
||||
public ReindexResponseBuilder withBatches(long batches) {
|
||||
this.batches = batches;
|
||||
return this;
|
||||
}
|
||||
@ -396,8 +426,8 @@ public class ReindexResponse {
|
||||
}
|
||||
|
||||
public ReindexResponse build() {
|
||||
return new ReindexResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries,
|
||||
searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures);
|
||||
return new ReindexResponse(took, timedOut, total, created, updated, deleted, batches, versionConflicts, noops,
|
||||
bulkRetries, searchRetries, throttledMillis, requestsPerSecond, throttledUntilMillis, failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.support;
|
||||
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
import static org.springframework.util.CollectionUtils.*;
|
||||
|
||||
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.Pageable;
|
||||
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.ElasticsearchOperations;
|
||||
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.mapping.ElasticsearchPersistentEntity;
|
||||
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.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
@ -116,25 +115,34 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
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()));
|
||||
// noinspection ConstantConditions
|
||||
SearchPage<T> page = SearchHitSupport.searchPageFor(searchHits, query.getPageable());
|
||||
// noinspection ConstantConditions
|
||||
return (Page<T>) SearchHitSupport.unwrapSearchHits(page);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterable<T> findAll(Sort sort) {
|
||||
|
||||
Assert.notNull(sort, "sort must not be null");
|
||||
|
||||
int itemCount = (int) this.count();
|
||||
|
||||
if (itemCount == 0) {
|
||||
return new PageImpl<>(Collections.emptyList());
|
||||
}
|
||||
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
|
||||
.withPageable(PageRequest.of(0, itemCount, sort)).build();
|
||||
Pageable pageable = PageRequest.of(0, itemCount, sort);
|
||||
Query query = Query.findAll();
|
||||
query.setPageable(pageable);
|
||||
List<SearchHit<T>> searchHitList = execute(
|
||||
operations -> operations.search(query, entityClass, getIndexCoordinates()).getSearchHits());
|
||||
// noinspection ConstantConditions
|
||||
return (List<T>) SearchHitSupport.unwrapSearchHits(searchHitList);
|
||||
}
|
||||
|
||||
@ -166,8 +174,8 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
|
||||
// noinspection ConstantConditions
|
||||
Query query = Query.findAll();
|
||||
((BaseQuery) query).setMaxResults(0);
|
||||
return execute(operations -> operations.count(query, entityClass, getIndexCoordinates()));
|
||||
}
|
||||
|
||||
@ -287,10 +295,9 @@ public class SimpleElasticsearchRepository<T, ID> implements ElasticsearchReposi
|
||||
@Override
|
||||
public void deleteAll() {
|
||||
IndexCoordinates indexCoordinates = getIndexCoordinates();
|
||||
Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
|
||||
|
||||
executeAndRefresh((OperationsCallback<Void>) operations -> {
|
||||
operations.delete(query, entityClass, indexCoordinates);
|
||||
operations.delete(Query.findAll(), entityClass, indexCoordinates);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@ -87,6 +88,7 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
@ -150,4 +152,40 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
||||
public String toString() {
|
||||
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
|
||||
@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class })
|
||||
@DisplayName("a sample JUnit 5 test with reactive rest client")
|
||||
public class JUnit5SampleReactiveRestClientBasedTests {
|
||||
public class JUnit5SampleReactiveERHLCTests {
|
||||
|
||||
@Autowired private ReactiveElasticsearchOperations elasticsearchOperations;
|
||||
|
@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||
@DisplayName("a sample JUnit 5 test with rest client")
|
||||
public class JUnit5SampleRestTemplateBasedTests {
|
||||
public class JUnit5SampleRestTemplateTests {
|
||||
|
||||
@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) {
|
||||
// Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses
|
||||
builder //
|
||||
.allowBlockingCallsInside("org.elasticsearch.Build", "<clinit>") //
|
||||
.allowBlockingCallsInside("org.elasticsearch.common.xcontent.XContentBuilder", "<clinit>") // pre 7.16
|
||||
.allowBlockingCallsInside("org.elasticsearch.common.XContentBuilder", "<clinit>") // 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 -> {
|
||||
new Error(it.toString()).printStackTrace();
|
||||
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 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.junit5.HoverflyExtension;
|
||||
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.params.ParameterizedTest;
|
||||
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.http.HttpHeaders;
|
||||
|
||||
@ -60,7 +63,7 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping;
|
||||
@ExtendWith(HoverflyExtension.class)
|
||||
public class RestClientsTest {
|
||||
|
||||
@ParameterizedTest // DATAES-700
|
||||
@ParameterizedTest(name = "{0}") // DATAES-700
|
||||
@MethodSource("clientUnderTestFactorySource")
|
||||
@DisplayName("should use configured proxy")
|
||||
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")
|
||||
@DisplayName("should configure client and set all required headers")
|
||||
void shouldConfigureClientAndSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) {
|
||||
|
||||
wireMockServer(server -> {
|
||||
|
||||
HttpHeaders defaultHeaders = new HttpHeaders();
|
||||
@ -112,17 +116,29 @@ public class RestClientsTest {
|
||||
return httpHeaders;
|
||||
});
|
||||
|
||||
if (clientUnderTestFactory instanceof RestClientUnderTestFactory) {
|
||||
if (clientUnderTestFactory instanceof ERHLCUnderTestFactory) {
|
||||
configurationBuilder
|
||||
.withClientConfigurer(RestClients.RestClientConfigurationCallback.from(httpClientBuilder -> {
|
||||
clientConfigurerCount.incrementAndGet();
|
||||
return httpClientBuilder;
|
||||
}));
|
||||
} else if (clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory) {
|
||||
} else if (clientUnderTestFactory instanceof ReactiveERHLCUnderTestFactory) {
|
||||
configurationBuilder.withClientConfigurer(ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
|
||||
clientConfigurerCount.incrementAndGet();
|
||||
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();
|
||||
@ -130,7 +146,8 @@ public class RestClientsTest {
|
||||
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
|
||||
|
||||
// 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();
|
||||
|
||||
verify(headRequestedFor(urlEqualTo("/")) //
|
||||
@ -140,7 +157,7 @@ public class RestClientsTest {
|
||||
.withHeader("def2", new EqualToPattern("def2-1")) //
|
||||
.withHeader("supplied", new EqualToPattern("val0")) //
|
||||
// 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 -> {
|
||||
|
||||
stubFor(put(urlMatching("^/index/_doc/42(\\?.*)$?")) //
|
||||
String urlPattern = "^/index/_doc/42(\\?.*)?$";
|
||||
stubFor(put(urlMatching(urlPattern)) //
|
||||
.willReturn(jsonResponse("{\n" + //
|
||||
" \"_id\": \"42\",\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("Content-Type", new EqualToPattern("application/vnd.elasticsearch+json;compatible-with=7")) //
|
||||
);
|
||||
@ -286,7 +304,15 @@ public class RestClientsTest {
|
||||
*/
|
||||
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
|
||||
protected String getDisplayName() {
|
||||
@ -323,7 +349,12 @@ public class RestClientsTest {
|
||||
}
|
||||
|
||||
@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.id("42");
|
||||
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
|
||||
protected String getDisplayName() {
|
||||
return "ReactiveElasticsearchClient";
|
||||
return "ElasticsearchClient";
|
||||
}
|
||||
|
||||
@Override
|
||||
ClientUnderTest create(ClientConfiguration clientConfiguration) {
|
||||
ReactiveElasticsearchClient client = ReactiveRestClients.create(clientConfiguration);
|
||||
|
||||
ElasticsearchClient client = ElasticsearchClients.createImperative(clientConfiguration);
|
||||
return new ClientUnderTest() {
|
||||
@Override
|
||||
public boolean ping() throws Exception {
|
||||
return client.ping().block();
|
||||
return client.ping().value();
|
||||
}
|
||||
|
||||
@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.id("42");
|
||||
indexRequest.source("{}", XContentType.JSON);
|
||||
indexRequest.source(entity, XContentType.JSON);
|
||||
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
|
||||
*
|
||||
@ -371,8 +475,9 @@ public class RestClientsTest {
|
||||
*/
|
||||
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
|
||||
return Stream.of( //
|
||||
new RestClientUnderTestFactory(), //
|
||||
new ReactiveElasticsearchClientUnderTestFactory() //
|
||||
);
|
||||
new ERHLCUnderTestFactory(), //
|
||||
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