Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bad6afdecc | |||
| a6a5e3f7f5 | |||
| b3a7a44db1 | |||
| ccf136c8e2 | |||
| 77da22598a | |||
| 86634ceb38 | |||
| 8377f64a8a | |||
| b549601d34 | |||
| 3298ba21ce | |||
| 18be558740 | |||
| 46cd4cd59e | |||
| a4ed7300d1 | |||
| b511756b2b | |||
| 68ba4cd39b | |||
| 6b39b39d35 | |||
| 4f4c99ec1f | |||
| 6cfb8eeea6 | |||
| c4910bf399 | |||
| c93039410a | |||
| 33c9180ccd | |||
| df5fd0b97c | |||
| 44a79093ce | |||
| cf135f4cdd | |||
| 954d8b0f97 | |||
| e71c66949a | |||
| acf02a1dc9 | |||
| 6ad449c9f4 | |||
| 94ebc40582 | |||
| ca39c1d24c | |||
| 2049726bcb | |||
| 7620222f89 | |||
| 1f1076aa8b | |||
| 3e950b8053 | |||
| 1c31a31e11 | |||
| 373be49f97 | |||
| 12b332223f | |||
| 47c0e186ec | |||
| 8b69a8a030 | |||
| 8bb0d8cb41 | |||
| 26a2f54c0f | |||
| 1754f59e70 | |||
| 8a166a1a40 | |||
| 5c029602b5 | |||
| 82a545912e | |||
| 587dd2edda | |||
| 451a22614d | |||
| 688f1722bd | |||
| b9dacc9a5e | |||
| e0acc5a2f9 | |||
| 094e79d601 | |||
| 81e2613669 | |||
| d974788ab8 | |||
| 0109a68c32 | |||
| 259c43af19 | |||
| a2802ce716 | |||
| f901380766 | |||
| 1e4b70ba6d | |||
| f917fb7a65 | |||
| bfc68699e0 | |||
| 96d0781f24 | |||
| 0a0fc75faa | |||
| ef880e6d3b | |||
| 3835e8e363 | |||
| 97d0566e94 | |||
| ac64a6a733 | |||
| c826adb152 | |||
| 1eaebdec33 | |||
| d37ca4d533 | |||
| d676ffe066 | |||
| 1bff5dd749 | |||
| 7cb46b6ab0 | |||
| a86658c397 | |||
| 0950dd6c7a | |||
| e1c926e134 | |||
| 6a201b4e34 | |||
| 48e2e9dea7 | |||
| a60f3059e1 | |||
| 3154c74f94 | |||
| 1407d4d3d6 | |||
| acd7990fac | |||
| b10eb75967 | |||
| a4952a90f8 | |||
| 50d241fcb4 | |||
| d1412f06a4 | |||
| 6bdeca64e2 | |||
| 9ef35ee46f | |||
| 102d654b92 | |||
| 3c0528796b | |||
| 6a976a666b | |||
| 57920d4e27 | |||
| 709b4c615e | |||
| 149535e0d3 | |||
| 6f6cedf6df | |||
| f91a4c18c4 | |||
| b5eee05e3b | |||
| 80c3a3bbc5 | |||
| 4eb8f08ad8 | |||
| 126e2b972d | |||
| 298d4ebce7 | |||
| a3414ff8e5 | |||
| 7365111244 | |||
| f70e4f817d | |||
| 0ac56b511c | |||
| 6adae6f61b | |||
| 6da0a78d99 | |||
| e856b974ad | |||
| bb8e593546 | |||
| f0c359eb41 | |||
| 4e39fb30bc | |||
| 5875bf3b9b | |||
| 33c4bb23b7 | |||
| 9c740f14bd | |||
| bf080002bc | |||
| 5eaea38ff1 | |||
| 0b0a388f32 | |||
| d8ebf2336b | |||
| 4b53be97da | |||
| c1a1ea9724 | |||
| 453460fab3 | |||
| cf380e289d | |||
| 266fb73126 | |||
| 2b2c07ae5f | |||
| 4495876025 | |||
| 0fa8de1bec | |||
| d34b00e436 | |||
| 9fa1d1ddbc | |||
| d56ed88917 | |||
| db59ac2a03 | |||
| 32fa7391c4 | |||
| e54c03d06a | |||
| 266dd573ae | |||
| f092d0ac19 | |||
| f37eec641b | |||
| 14cc9ea495 | |||
| 4a0e7cc56e | |||
| c5db583048 | |||
| cf3e46bf68 | |||
| 1331e26279 | |||
| 056a4a76e7 | |||
| 3c060d8891 | |||
| 739c6e4be9 | |||
| 7843cd3db6 | |||
| 80e8a5efb4 | |||
| 729893f297 | |||
| ae2905d519 | |||
| e8ef6b822f |
@@ -20,3 +20,7 @@ target
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
/.env
|
||||
|
||||
|
||||
/zap.env
|
||||
|
||||
+84
-86
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
#Mon Oct 11 14:30:32 CEST 2021
|
||||
#Fri Jun 03 09:32:51 CEST 2022
|
||||
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
|
||||
|
||||
@@ -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.
|
||||
|
||||
Vendored
+18
-96
@@ -1,3 +1,9 @@
|
||||
def p = [:]
|
||||
node {
|
||||
checkout scm
|
||||
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
|
||||
}
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
|
||||
@@ -12,10 +18,11 @@ pipeline {
|
||||
}
|
||||
|
||||
stages {
|
||||
stage("test: baseline (jdk8)") {
|
||||
stage("test: baseline (Java 17)") {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -25,14 +32,14 @@ pipeline {
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
DOCKER_HUB = credentials("${p['docker.credentials']}")
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
@@ -42,68 +49,11 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
allOf {
|
||||
branch 'main'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
parallel {
|
||||
stage("test: baseline (jdk11)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=java11 ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk17)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('openjdk:17-bullseye').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=java11 ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -113,13 +63,13 @@ pipeline {
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
@@ -133,34 +83,6 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish documentation') {
|
||||
when {
|
||||
branch 'main'
|
||||
}
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.distribution-repository=temp-private-local " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
+26
-3
@@ -12,13 +12,35 @@ This project is lead and maintained by the community.
|
||||
== Features
|
||||
|
||||
* Spring configuration support using Java based `@Configuration` classes or an XML namespace for a ES clients instances.
|
||||
* `ElasticsearchRestTemplate` helper class that increases productivity performing common ES operations.
|
||||
* `ElasticsearchOperations` class and implementations that increases productivity performing common ES operations.
|
||||
Includes integrated object mapping between documents and POJOs.
|
||||
* Feature Rich Object Mapping integrated with Spring’s Conversion Service
|
||||
* Annotation based mapping metadata
|
||||
* Automatic implementation of `Repository` interfaces including support for custom search methods.
|
||||
* CDI support for repositories
|
||||
|
||||
== About Elasticsearch versions and clients
|
||||
|
||||
=== Elasticsearch 7.17 client libraries
|
||||
|
||||
At the end of 2021 Elasticsearch with version 7.17 released the new version of their Java client and deprecated the `RestHighLevelCLient` which was the default way to access Elasticsearch up to then.
|
||||
|
||||
Spring Data Elasticsearch will in version 4.4 offer the possibility to optionally use the new client as an alternative to the existing setup using the `RestHighLevelCLient`.
|
||||
The default client that is used still is the `RestHighLevelCLient`, first because the integration of the new client is not yet complete, the new client still has features missing and bugs which will hopefully be resolved soon.
|
||||
Second, and more important, the new Elasticsearch client forces users to switch from using `javax.json.spi.JsonProvider` to `jakarta.json.spi.JsonProvider`.
|
||||
Spring Data Elasticsearch cannot enforce this switch; Spring Boot will switch to `jakarta` with version 3 and then it's safe for Spring Data Elasticsearch to switch to the new client.
|
||||
|
||||
So for version 4.4 Spring Data Elasticsearch will keep using the `RestHighLevelCLient` in version 7.17.x (as long as this will be available).
|
||||
|
||||
=== Elasticsearch 8 client libraries
|
||||
|
||||
In Elasticsearch 8, the `RestHighLevelCLient` has been removed.
|
||||
This means that a switch to this client version can only be done with the next major upgrade which will be Spring Data Elasticsearch 5, based on Spring Data 3, used by Spring Boot 3, based on Spring 6 and Java 17.
|
||||
|
||||
=== Elasticsearch 8 cluster
|
||||
|
||||
It should be possible to use the Elasticsearch 7 client to access a cluster running version 8 by setting the appropriate compatibility headers (see the documentation at https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.clients.configuration). but I encountered and heard of cases where the response from the server is not parseable by the client although the headers are set, so use with care.
|
||||
|
||||
== Code of Conduct
|
||||
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct].
|
||||
@@ -168,7 +190,9 @@ Attach a link to your code or a compressed file containing your code.
|
||||
== Building from Source
|
||||
|
||||
You don’t need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
|
||||
You also need JDK 1.8.
|
||||
|
||||
You need JDK 17 or above to build the _main_ branch.
|
||||
For the branches up to and including release 4.4, JDK 8 is required.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
@@ -182,7 +206,6 @@ _Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull r
|
||||
IMPORTANT: When contributing, please make sure an issue exists in https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] and comment on this issue with how you want to address it.
|
||||
By this we not only know that someone is working on an issue, we can also align architectural questions and possible solutions before work is invested . We so can prevent that much work is put into Pull Requests that have little or no chances of being merged.
|
||||
|
||||
|
||||
=== Building reference documentation
|
||||
|
||||
Building the documentation builds also the project without running tests.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.3_7-jdk
|
||||
|
||||
# Docker container images - standard
|
||||
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.12
|
||||
docker.mongodb.5.0.version=5.0.6
|
||||
|
||||
# Supported versions of Redis
|
||||
docker.redis.6.version=6.2.6
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.12
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
|
||||
|
||||
# Credentials
|
||||
docker.registry=
|
||||
docker.credentials=hub.docker.com-springbuildmaster
|
||||
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
|
||||
@@ -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" ]
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>4.4.0-M1</version>
|
||||
<version>5.0.0-M6</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.7.0-M1</version>
|
||||
<version>3.0.0-M6</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,12 +18,22 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<elasticsearch>7.16.2</elasticsearch>
|
||||
<log4j>2.17.0</log4j>
|
||||
<springdata.commons>3.0.0-M6</springdata.commons>
|
||||
|
||||
<!-- version of the RestHighLevelClient -->
|
||||
<elasticsearch-rhlc>7.17.5</elasticsearch-rhlc>
|
||||
<!-- version of the new ElasticsearchClient -->
|
||||
<elasticsearch-java>8.3.3</elasticsearch-java>
|
||||
|
||||
<log4j>2.18.0</log4j>
|
||||
<netty>4.1.65.Final</netty>
|
||||
<springdata.commons>2.7.0-M1</springdata.commons>
|
||||
<testcontainers>1.16.2</testcontainers>
|
||||
|
||||
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
|
||||
<hoverfly>0.14.2</hoverfly>
|
||||
<jsonassert>1.5.1</jsonassert>
|
||||
<testcontainers>1.17.3</testcontainers>
|
||||
<wiremock>2.33.2</wiremock>
|
||||
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
|
||||
<!--
|
||||
@@ -134,11 +144,36 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Elasticsearch -->
|
||||
<!-- optional Elasticsearch RestHighLevelClient, deprecated in SDE 5.0 -->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<version>${elasticsearch-rhlc}</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</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-java}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
@@ -174,23 +209,40 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.enterprise</groupId>
|
||||
<artifactId>cdi-api</artifactId>
|
||||
<groupId>jakarta.enterprise</groupId>
|
||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
||||
<version>${cdi}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax-annotation-api}</version>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
<version>${jakarta-annotation-api}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans</groupId>
|
||||
<artifactId>openwebbeans-se</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${webbeans}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans</groupId>
|
||||
<artifactId>openwebbeans-spi</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${webbeans}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans</groupId>
|
||||
<artifactId>openwebbeans-impl</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>${webbeans}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
@@ -208,6 +260,25 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>log4j-over-slf4j</artifactId>
|
||||
<version>${slf4j}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>${log4j}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-to-slf4j</artifactId>
|
||||
<version>${log4j}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.tools</groupId>
|
||||
<artifactId>blockhound-junit-platform</artifactId>
|
||||
@@ -236,14 +307,14 @@
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<version>${jsonassert}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>2.32.0</version>
|
||||
<version>${wiremock}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- these exclusions are needed because of Elasticsearch JarHell-->
|
||||
@@ -261,7 +332,7 @@
|
||||
<dependency>
|
||||
<groupId>io.specto</groupId>
|
||||
<artifactId>hoverfly-java-junit5</artifactId>
|
||||
<version>0.14.1</version>
|
||||
<version>${hoverfly}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -374,7 +445,7 @@
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.5</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.pitest</groupId>
|
||||
@@ -383,6 +454,9 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<jvmArgs>
|
||||
<jvmArg>-XX:+AllowRedefinitionToAddDeleteMethods</jvmArg>
|
||||
</jvmArgs>
|
||||
<excludedGroups>integration-test</excludedGroups>
|
||||
<targetClasses>
|
||||
<param>org.springframework.data.elasticsearch.core.geo.*</param>
|
||||
@@ -399,7 +473,7 @@
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
@@ -461,6 +535,7 @@
|
||||
<id>local-maven-repo</id>
|
||||
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
|
||||
</repository>
|
||||
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
|
||||
@@ -29,15 +29,19 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
|
||||
[[preface.versions]]
|
||||
=== Versions
|
||||
|
||||
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring Data release train:
|
||||
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of
|
||||
Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring
|
||||
Data release train. The Elasticsearch version given shows with which client libraries Spring Data Elasticsearch was
|
||||
built and tested.
|
||||
|
||||
[cols="^,^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
|
||||
| 2022.0 (Raj) | 4.4.x | 7.16.2 | 5.3.x | 2.7.x
|
||||
| 2022.0 (Turing) | 5.0.x | 8.3.3 | 6.0.x | 3.0.x?
|
||||
| 2021.2 (Raj) | 4.4.x | 7.17.3 | 5.3.x | 2.7.x
|
||||
| 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x
|
||||
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x
|
||||
| 2020.0 (Ockham)footnote:oom[Out of maintenance] | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
|
||||
| 2021.0 (Pascal) | 4.2.xfootnote:oom[Out of maintenance] | 7.12.0 | 5.3.x | 2.5.x
|
||||
| 2020.0 (Ockham)footnote:oom[] | 4.1.xfootnote:oom[] | 7.9.3 | 5.3.2 | 2.4.x
|
||||
| Neumannfootnote:oom[] | 4.0.xfootnote:oom[] | 7.6.2 | 5.2.12 |2.3.x
|
||||
| Moorefootnote:oom[] | 3.2.xfootnote:oom[] |6.8.12 | 5.2.12| 2.2.x
|
||||
| Lovelacefootnote:oom[] | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
|
||||
@@ -45,4 +49,5 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
|
||||
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 4.3.25 | 1.5.x
|
||||
|===
|
||||
|
||||
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
|
||||
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming
|
||||
the usage of the <<elasticsearch.operations,ElasticsearchOperations interface>>.
|
||||
|
||||
@@ -6,15 +6,106 @@ This chapter illustrates configuration and usage of supported Elasticsearch clie
|
||||
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
|
||||
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
|
||||
|
||||
[[elasticsearch.clients.rest]]
|
||||
== High Level REST Client
|
||||
[[elasticsearch.clients.restclient]]
|
||||
== Imperative Rest Client
|
||||
|
||||
The Java High Level REST Client is the default client of Elasticsearch, it is configured like shown:
|
||||
To use the imperative (non-reactive) client, a configuration bean must be configured like this:
|
||||
|
||||
.High Level REST Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@Autowired
|
||||
ElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
----
|
||||
|
||||
the following can be injected:
|
||||
|
||||
<.> an implementation of `ElasticsearchOperations`
|
||||
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
|
||||
This is new Elasticsearch client implementation.
|
||||
<.> the low level `RestClient` from the Elasticsearch libraries
|
||||
====
|
||||
|
||||
Basically one should just use the `ElasticsearchOperations` to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.reactiverestclient]]
|
||||
== Reactive Rest Client
|
||||
|
||||
When working with the reactive stack, the configuration must be derived from a different class:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
@Autowired
|
||||
ReactiveElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
----
|
||||
|
||||
the following can be injected:
|
||||
|
||||
<.> an implementation of `ElasticsearchOperations`
|
||||
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
|
||||
This is based on the new Elasticsearch client implementation.
|
||||
<.> the low level `RestClient` from the Elasticsearch libraries
|
||||
====
|
||||
|
||||
Basically one should just use the `ReactiveElasticsearchOperations` to interact with the Elasticsearch cluster.
|
||||
When using repositories, this instance is used under the hood as well.
|
||||
|
||||
[[elasticsearch.clients.resthighlevelclient]]
|
||||
== High Level REST Client (deprecated)
|
||||
|
||||
The Java RestHighLevelClient is deprecated, but still can be configured like shown (read
|
||||
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well):
|
||||
|
||||
.RestHighLevelClient
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.erhlc.AbstractElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
|
||||
@@ -53,16 +144,21 @@ IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.reactive]]
|
||||
== Reactive Client
|
||||
== Reactive Client (deprecated)
|
||||
|
||||
The `ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
|
||||
The `org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
|
||||
It uses the request/response objects provided by the Elasticsearch core project.
|
||||
Calls are directly operated on the reactive stack, **not** wrapping async (thread pool bound) responses into reactive types.
|
||||
|
||||
.Reactive REST Client
|
||||
This was the first reactive implementation Spring Data Elasticsearch provided, but now is deprecated in favour of the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient`
|
||||
which uses the functionality offered by the new Elasticsearch client libraries.
|
||||
|
||||
.Reactive REST Client (deprecated)
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@@ -89,8 +185,6 @@ Mono<IndexResponse> response = client.index(request ->
|
||||
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
|
||||
====
|
||||
|
||||
NOTE: The ReactiveClient response, especially for search operations, is bound to the `from` (offset) & `size` (limit) options of the request.
|
||||
|
||||
[[elasticsearch.clients.configuration]]
|
||||
== Client Configuration
|
||||
|
||||
@@ -118,12 +212,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
return headers;
|
||||
})
|
||||
.withClientConfigurer( <.>
|
||||
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
|
||||
// ...
|
||||
return webClient;
|
||||
}))
|
||||
.withClientConfigurer( <.>
|
||||
RestClients.RestClientConfigurationCallback.from(clientBuilder -> {
|
||||
ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
|
||||
// ...
|
||||
return clientBuilder;
|
||||
}))
|
||||
@@ -144,13 +233,77 @@ Default is 5 sec.
|
||||
<.> Optionally set headers.
|
||||
<.> Add basic authentication.
|
||||
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
|
||||
<.> for reactive setup a function configuring the `WebClient`
|
||||
<.> for non-reactive setup a function configuring the REST client
|
||||
<.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added multiple times.
|
||||
====
|
||||
|
||||
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
|
||||
If this is used in the reactive setup, the supplier function *must not* block!
|
||||
|
||||
[[elasticsearch.clients.configuration.callbacks]]
|
||||
=== Client configuration callbacks
|
||||
|
||||
The `ClientConfiguration` class offers the most common parameters to configure the client. In the case this is not
|
||||
enough, the user can add callback functions by using the `withClientConfigurer(ClientConfigurationCallback<?>)` method.
|
||||
|
||||
The following callbacks are provided:
|
||||
|
||||
==== Configuration of the low level Elasticsearch `RestClient`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch RestClient
|
||||
return restClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
=== Elasticsearch 7 compatibility headers
|
||||
|
||||
When using the deprecated `RestHighLevelClient` and accessing an Elasticsearch cluster that is running on version 8, it is necessary to set the compatibility headers
|
||||
https://www.elastic.co/guide/en/elasticsearch/reference/8.0/rest-api-compatibility.html[see Elasticsearch
|
||||
documentation].
|
||||
|
||||
For the imperative client this must be done by setting the default headers, for the reactive code this must be done using a header supplier:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
HttpHeaders compatibilityHeaders = new HttpHeaders();
|
||||
compatibilityHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7");
|
||||
compatibilityHeaders.add("Content-Type", "application/vnd.elasticsearch+json;"
|
||||
+ "compatible-with=7");
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200")
|
||||
.withProxy("localhost:8080")
|
||||
.withBasicAuth("elastic","hcraescitsale")
|
||||
.withDefaultHeaders(compatibilityHeaders) // this variant for imperative code
|
||||
.withHeaders(() -> compatibilityHeaders) // this variant for reactive code
|
||||
.build();
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.logging]]
|
||||
== Client Logging
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ void init() {
|
||||
|
||||
== Retrieving data
|
||||
|
||||
Currently native search queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
|
||||
Currently native queries must be used to query the data, so there is no support from standard repository methods. <<repositories.custom-implementations>> can be used instead.
|
||||
|
||||
The following code shows as an example how to retrieve all entries that have a _vote_ (which must be _answers_, because only answers can have a vote) using an `ElasticsearchOperations` instance:
|
||||
|
||||
@@ -219,11 +219,17 @@ The following code shows as an example how to retrieve all entries that have a _
|
||||
[source,java]
|
||||
----
|
||||
SearchHits<Statement> hasVotes() {
|
||||
NativeSearchQuery query = new NativeSearchQueryBuilder()
|
||||
.withQuery(hasChildQuery("vote", matchAllQuery(), ScoreMode.None))
|
||||
.build();
|
||||
|
||||
return operations.search(query, Statement.class);
|
||||
Query query = NativeQuery.builder()
|
||||
.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb //
|
||||
.hasChild(hc -> hc
|
||||
.queryName("vote") //
|
||||
.query(matchAllQueryAsQuery()) //
|
||||
.scoreMode(ChildScoreMode.None)//
|
||||
)))
|
||||
.build();
|
||||
|
||||
return operations.search(query, Statement.class);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -14,7 +14,7 @@ The parameters of the `@Document` annotation that are relevant for the index se
|
||||
== Removals
|
||||
|
||||
The `@Score` annotation that was used to set the score return value in an entity was deprecated in version 4.0 and has been removed.
|
||||
Scroe values are returned in the `SearchHit` instances that encapsulate the returned entities.
|
||||
Score values are returned in the `SearchHit` instances that encapsulate the returned entities.
|
||||
|
||||
The `org.springframework.data.elasticsearch.ElasticsearchException` class has been removed.
|
||||
The remaining usages have been replaced with `org.springframework.data.mapping.MappingException` and `org.springframework.dao.InvalidDataAccessApiUsageException`.
|
||||
|
||||
@@ -6,14 +6,167 @@ 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.
|
||||
|
||||
=== Behaviour change
|
||||
|
||||
The `ReactiveElasticsearchTemplate`, when created directly or by Spring Boot configuration had a default refresh policy of IMMEDIATE.
|
||||
This could cause performance issues on heavy indexing and was different than the default behaviour of Elasticsearch.
|
||||
This has been changed to that now the default refresh policy is NONE.
|
||||
When the
|
||||
`ReactiveElasticsearchTemplate` was provided by using the configuration like described in <<elasticsearch.clients.reactive>> the default refresh policy already was set to NONE.
|
||||
|
||||
[[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 <<elasticsearch.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.3</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.3</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`
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
[[elasticsearch-migration-guide-4.4-5.0]]
|
||||
= Upgrading from 4.4.x to 5.0.x
|
||||
|
||||
This section describes breaking changes from version 4.4.x to 5.0.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-4.5.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
=== `org.springframework.data.elasticsearch.client.erhlc` package
|
||||
|
||||
See <<elasticsearch-migration-guide-4.4-5.0.breaking-changes-packages>>, all classes in this package have been deprecated, as the default client implementations to use are the ones based on the new Java Client from Elasticsearch, see <<elasticsearch-migration-guide-4.4-5.0.new-clients>>
|
||||
|
||||
=== Removal of deprecated code
|
||||
|
||||
`DateFormat.none` and `DateFormat.custom` had been deprecated since version 4.2 and have been removed.
|
||||
|
||||
The properties of `@Document` that were deprecated since 4.2 have been removed.
|
||||
Use the `@Settings` annotation for these.
|
||||
|
||||
`@DynamicMapping` and `@DynamicMappingValue` have been removed.
|
||||
Use `@Document.dynamic` or `@Field.dynamic` instead.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
=== Removal of deprecated calls
|
||||
|
||||
==== suggest calls in operations interfaces have been removed
|
||||
|
||||
Both `SearchOperations` and `ReactiveSearchOperations` had deprecated calls that were using Elasticsearch classes as parameters.
|
||||
These now have been removed and so the dependency on Elasticsearch classes in these APIs has been cleaned.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-packages]]
|
||||
=== Package changes
|
||||
|
||||
All the classes that are using or depend on the deprecated Elasticsearch `RestHighLevelClient` have been moved to the package `org.springframework.data.elasticsearch.client.erhlc`.
|
||||
By this change we now have a clear separation of code using the old deprecated Elasticsearch libraries, code using the new Elasticsearch client and code that is independent of the client implementation.
|
||||
Also the reactive implementation that was provided up to now has been moved here, as this implementation contains code that was copied and adapted from Elasticsearch libraries.
|
||||
|
||||
If you are using `ElasticsearchRestTemplate` directly and not the `ElasticsearchOperations` interface you'll need to adjust your imports as well.
|
||||
|
||||
When working with the `NativeSearchQuery` class, you'll need to switch to the `NativeQuery` class, which can take a
|
||||
`Query` instance comign from the new Elasticsearch client libraries.
|
||||
You'll find plenty of examples in the test code.
|
||||
|
||||
=== Conversion to Java 17 records
|
||||
|
||||
The following classes have been converted to `Record`, you might need to adjust the use of getter methods from
|
||||
`getProp()` to `prop()`:
|
||||
|
||||
* `org.springframework.data.elasticsearch.core.AbstractReactiveElasticsearchTemplate.IndexResponseMetaData`
|
||||
* `org.springframework.data.elasticsearch.core.ActiveShardCount`
|
||||
* `org.springframework.data.elasticsearch.support.Version`
|
||||
* `org.springframework.data.elasticsearch.support.ScoreDoc`
|
||||
* `org.springframework.data.elasticsearch.core.query.ScriptData`
|
||||
* `org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm`
|
||||
|
||||
=== New HttpHeaders class
|
||||
|
||||
Until version 4.4 the client configuration used the `HttpHeaders` class from the `org.springframework:spring-web`
|
||||
project.
|
||||
This introduces a dependency on that artifact.
|
||||
Users that do not use spring-web then face an error as this class cannot be found.
|
||||
|
||||
In version 5.0 we introduce our own `HttpHeaders` to configure the clients.
|
||||
|
||||
So if you are using headers in the client configuration, you need to replace `org.springframework.http.HttpHeaders`
|
||||
with `org.springframework.data.elasticsearch.support.HttpHeaders`.
|
||||
|
||||
Hint: You can pass a `org.springframework.http
|
||||
.HttpHeaders` to the `addAll()` method of `org.springframework.data.elasticsearch.support.HttpHeaders`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.new-clients]]
|
||||
== New Elasticsearch client
|
||||
|
||||
Spring Data Elasticsearch now uses the new `ElasticsearchClient` and has deprecated the use of the previous `RestHighLevelClient`.
|
||||
|
||||
=== Imperative style configuration
|
||||
|
||||
To 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 configuration
|
||||
|
||||
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`
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.old-client]]
|
||||
=== Still want to use the old client?
|
||||
|
||||
The old deprecated `RestHighLevelClient` can still be used, but you will need to add the dependency explicitly to your application as Spring Data Elasticsearch does not pull it in automatically anymore:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<!-- include the RHLC, specify version explicitly -->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>7.17.5</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
----
|
||||
====
|
||||
|
||||
Make sure to specify the version 7.17.5 explicitly, otherwise maven will resolve to 8.3.3, and this does not exist.
|
||||
@@ -87,12 +87,19 @@ private ElasticsearchOperations operations;
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFilter(boolFilter().must(termFilter("id", documentId)))
|
||||
.build();
|
||||
Query query = NativeQuery.builder()
|
||||
.withQuery(q -> q
|
||||
.matchAll(ma -> ma))
|
||||
.withFilter( q -> q
|
||||
.bool(b -> b
|
||||
.must(m -> m
|
||||
.term(t -> t
|
||||
.field("id")
|
||||
.value(documentId))
|
||||
)))
|
||||
.build();
|
||||
|
||||
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
|
||||
SearchHits<SampleEntity> sampleEntities = operations.search(query, SampleEntity.class, index);
|
||||
----
|
||||
====
|
||||
|
||||
@@ -107,13 +114,15 @@ This is internally used by Spring Data Elasticsearch to provide the implementati
|
||||
----
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
Query searchQuery = NativeQuery.builder()
|
||||
.withQuery(q -> q
|
||||
.matchAll(ma -> ma))
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
|
||||
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
|
||||
index);
|
||||
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
while (stream.hasNext()) {
|
||||
@@ -124,23 +133,28 @@ stream.close();
|
||||
----
|
||||
====
|
||||
|
||||
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
|
||||
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this,
|
||||
the following methods of the `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
|
||||
different `ElasticsearchOperations` implementations:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@Autowired ElasticsearchRestTemplate template;
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
|
||||
AbstractElasticsearchTemplate template = (AbstractElasticsearchTemplate)operations;
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
Query query = NativeQuery.builder()
|
||||
.withQuery(q -> q
|
||||
.matchAll(ma -> ma))
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, searchQuery, SampleEntity.class, index);
|
||||
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, query, SampleEntity.class, index);
|
||||
|
||||
String scrollId = scroll.getScrollId();
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
@@ -261,3 +275,42 @@ SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.
|
||||
====
|
||||
|
||||
This works with every implementation of the `Query` interface.
|
||||
|
||||
[[elasticsearch.misc.point-in-time]]
|
||||
== Point In Time (PIT) API
|
||||
|
||||
`ElasticsearchOperations` supports the point in time API of Elasticsearch (see https://www.elastic
|
||||
.co/guide/en/elasticsearch/reference/8.3/point-in-time-api.html). The following code snippet shows how to use this
|
||||
feature with a fictional `Person` class:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ElasticsearchOperations operations; // autowired
|
||||
Duration tenSeconds = Duration.ofSeconds(10);
|
||||
|
||||
String pit = operations.openPointInTime(IndexCoordinates.of("person"), tenSeconds); <.>
|
||||
|
||||
// create query for the pit
|
||||
Query query1 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Smith"))
|
||||
.withPointInTime(new Query.PointInTime(pit, tenSeconds)) <.>
|
||||
.build();
|
||||
SearchHits<Person> searchHits1 = operations.search(query1, Person.class);
|
||||
// do something with the data
|
||||
|
||||
// create 2nd query for the pit, use the id returned in the previous result
|
||||
Query query2 = new CriteriaQueryBuilder(Criteria.where("lastName").is("Miller"))
|
||||
.withPointInTime(
|
||||
new Query.PointInTime(searchHits1.getPointInTimeId(), tenSeconds)) <.>
|
||||
.build();
|
||||
SearchHits<Person> searchHits2 = operations.search(query2, Person.class);
|
||||
// do something with the data
|
||||
|
||||
operations.closePointInTime(searchHits2.getPointInTimeId()); <.>
|
||||
|
||||
----
|
||||
<.> create a point in time for an index (can be multiple names) and a keep-alive duration and retrieve its id
|
||||
<.> pass that id into the query to search together with the next keep-alive value
|
||||
<.> for the next query, use the id returned from the previous search
|
||||
<.> when done, close the point in time using the last returned id
|
||||
====
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.5-0-0]]
|
||||
== New in Spring Data Elasticsearch 5.0
|
||||
|
||||
* Upgrade to Java 17 baseline
|
||||
* Upgrade to Spring Framework 6
|
||||
* Use the new Elasticsearch client library
|
||||
|
||||
[[new-features.4-4-0]]
|
||||
== New in Spring Data Elasticsearch 4.4
|
||||
|
||||
* Upgrade to Elasticsearch 7.16.2.
|
||||
* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client
|
||||
* Upgrade to Elasticsearch 7.17.3.
|
||||
|
||||
[[new-features.4-3-0]]
|
||||
== New in Spring Data Elasticsearch 4.3
|
||||
|
||||
@@ -30,36 +30,10 @@ There is support for automatic creation of indices and writing the mappings when
|
||||
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.resttemplate]]
|
||||
== ElasticsearchRestTemplate
|
||||
|
||||
The `ElasticsearchRestTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.rest>>.
|
||||
|
||||
.ElasticsearchRestTemplate configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() { <1>
|
||||
return RestClients.create(ClientConfiguration.localhost()).rest();
|
||||
}
|
||||
|
||||
// no special bean creation needed <2>
|
||||
}
|
||||
----
|
||||
|
||||
<1> Setting up the <<elasticsearch.clients.rest>>.
|
||||
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.usage]]
|
||||
== Usage examples
|
||||
|
||||
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different.
|
||||
The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
|
||||
The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
|
||||
|
||||
.ElasticsearchOperations usage
|
||||
====
|
||||
@@ -150,7 +124,7 @@ An Iterator returned by the streaming functions of the `SearchOperations` interf
|
||||
[[elasticsearch.operations.queries]]
|
||||
== Queries
|
||||
|
||||
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeSearchQuery`.
|
||||
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeQuery`.
|
||||
|
||||
[[elasticsearch.operations.criteriaquery]]
|
||||
=== CriteriaQuery
|
||||
@@ -230,7 +204,7 @@ The following code shows a query that searches for persons having the first name
|
||||
[source,java]
|
||||
----
|
||||
|
||||
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
|
||||
Query query = new StringQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
|
||||
----
|
||||
@@ -238,21 +212,28 @@ SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
|
||||
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
|
||||
|
||||
[[elasticsearch.operations.nativesearchquery]]
|
||||
=== NativeSearchQuery
|
||||
[[elasticsearch.operations.nativequery]]
|
||||
=== NativeQuery
|
||||
|
||||
`NativeSearchQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
|
||||
It allows to use all the different `QueryBuilder` implementations from the Elasticsearch library therefore named "native".
|
||||
`NativeQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
|
||||
It allows to use all the different `co.elastic.clients.elasticsearch._types.query_dsl.Query` implementations from the Elasticsearch library therefore named "native".
|
||||
|
||||
The following code shows how to search for persons with a given firstname and for the found documents have a terms aggregation that counts the number of occurences of the lastnames for these persons:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Query query = new NativeSearchQueryBuilder()
|
||||
.addAggregation(terms("lastnames").field("lastname").size(10)) //
|
||||
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
|
||||
.build();
|
||||
Query query = NativeQuery.builder()
|
||||
.withAggregation("lastNames", Aggregation.of(a -> a
|
||||
.terms(ta -> ta.field("last-name").size(10))))
|
||||
.withQuery(q -> q
|
||||
.match(m -> m
|
||||
.field("firstName")
|
||||
.query(firstName)
|
||||
)
|
||||
)
|
||||
.withPageable(pageable)
|
||||
.build();
|
||||
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
----
|
||||
|
||||
@@ -53,7 +53,7 @@ interface BookRepository extends Repository<Book, String> {
|
||||
@HighlightField(name = "name"),
|
||||
@HighlightField(name = "summary")
|
||||
})
|
||||
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
|
||||
SearchHits<Book> findByNameOrSummary(String text, String summary);
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -62,6 +62,30 @@ It is possible to define multiple fields to be highlighted like above, and both
|
||||
|
||||
In the search results the highlight data can be retrieved from the `SearchHit` class.
|
||||
|
||||
=== @SourceFilters
|
||||
|
||||
Sometimes the user does not need to have all the properties of an entity returned from a search but only a subset.
|
||||
Elasticsearch provides source filtering to reduce the amount of data that is transferred across the network to the
|
||||
application.
|
||||
|
||||
When working with `Query` implementations and the `ElasticsearchOperations` this is easily possible by setting a
|
||||
source filter on the query.
|
||||
|
||||
When using repository methods there is the `@SourceFilters` annotation:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends Repository<Book, String> {
|
||||
|
||||
@SourceFilters(includes = "name")
|
||||
SearchHits<Book> findByName(String text);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
In this example, all the properties of the returned `Book` objects would be `null` except the name.
|
||||
|
||||
[[elasticsearch.annotation]]
|
||||
== Annotation based configuration
|
||||
|
||||
|
||||
@@ -312,8 +312,9 @@ Repository methods can be defined to have the following return types for returni
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
== Using @Query Annotation
|
||||
|
||||
.Declare query at the method using the `@Query` annotation.
|
||||
.Declare query on the method using the `@Query` annotation.
|
||||
====
|
||||
The arguments passed to the method can be inserted into placeholders in the query string. the placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
|
||||
[source,java]
|
||||
----
|
||||
interface BookRepository extends ElasticsearchRepository<Book, String> {
|
||||
@@ -338,3 +339,24 @@ It will be sent to Easticsearch as value of the query element; if for example th
|
||||
}
|
||||
----
|
||||
====
|
||||
.`@Query` annotation on a method taking a Collection argument
|
||||
====
|
||||
A repository method such as
|
||||
[source,java]
|
||||
----
|
||||
@Query("{\"ids\": {\"values\": ?0 }}")
|
||||
List<SampleEntity> getByIds(Collection<String> ids);
|
||||
----
|
||||
would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-ids-query.html[IDs query] to return all the matching documents. So calling the method with a `List` of `["id1", "id2", "id3"]` would produce the query body
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"query": {
|
||||
"ids": {
|
||||
"values": ["id1", "id2", "id3"]
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
@@ -10,4 +10,9 @@ 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[]
|
||||
|
||||
include::elasticsearch-migration-guide-4.4-5.0.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;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,14 @@ public class NoSuchIndexException extends NonTransientDataAccessResourceExceptio
|
||||
|
||||
private final String index;
|
||||
|
||||
/**
|
||||
* @since 4.4
|
||||
*/
|
||||
public NoSuchIndexException(String index) {
|
||||
super(String.format("Index %s not found.", index));
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public NoSuchIndexException(String index, Throwable cause) {
|
||||
super(String.format("Index %s not found.", index), cause);
|
||||
this.index = index;
|
||||
|
||||
+40
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken
|
||||
* from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the
|
||||
* same values as the Elasticsearch formatter.
|
||||
* Values based on <a href="https://www.elastic.co/guide/reference/mapping/date-format/">Elasticsearch reference
|
||||
* documentation</a>. The patterns are taken from this documentation and slightly adapted so that a Java
|
||||
* {@link java.time.format.DateTimeFormatter} produces the same values as the Elasticsearch formatter. Use
|
||||
* <code>format = {}</code> to disable built-in date formats in the {@link Field} annotation. If you want to use only a
|
||||
* custom date format pattern, you must set the <code>format</code> property to empty <code>{}</code>.
|
||||
*
|
||||
* @author Jakub Vavrik
|
||||
* @author Tim te Beek
|
||||
@@ -26,19 +28,6 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
public enum DateFormat {
|
||||
/**
|
||||
* @deprecated since 4.2, will be removed in a future version. Use <code>format = {}</code> to disable built-in date
|
||||
* formats in the @Field annotation.
|
||||
*/
|
||||
@Deprecated
|
||||
none(""), //
|
||||
/**
|
||||
* @deprecated since 4.2, will be removed in a future version.It is no longer required for using a custom date format
|
||||
* pattern. If you want to use only a custom date format pattern, you must set the <code>format</code>
|
||||
* property to empty <code>{}</code>.
|
||||
*/
|
||||
@Deprecated
|
||||
custom(""), //
|
||||
basic_date("uuuuMMdd"), //
|
||||
basic_date_time("uuuuMMdd'T'HHmmss.SSSXXX"), //
|
||||
basic_date_time_no_millis("uuuuMMdd'T'HHmmssXXX"), //
|
||||
@@ -60,7 +49,7 @@ public enum DateFormat {
|
||||
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
|
||||
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
|
||||
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
|
||||
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation
|
||||
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
|
||||
epoch_millis("epoch_millis"), //
|
||||
epoch_second("epoch_second"), //
|
||||
hour("HH"), //
|
||||
|
||||
@@ -44,7 +44,7 @@ public @interface Document {
|
||||
* Name of the Elasticsearch index.
|
||||
* <ul>
|
||||
* <li>Lowercase only</li>
|
||||
* <li>Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #</li>
|
||||
* <li>Cannot include \, /, *, ?, ", >, <, |, ` ` (space character), ,, #</li>
|
||||
* <li>Cannot start with -, _, +</li>
|
||||
* <li>Cannot be . or ..</li>
|
||||
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
|
||||
@@ -53,49 +53,6 @@ public @interface Document {
|
||||
*/
|
||||
String indexName();
|
||||
|
||||
/**
|
||||
* Use server-side settings when creating the index.
|
||||
*
|
||||
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
|
||||
*/
|
||||
@Deprecated
|
||||
boolean useServerConfiguration() default false;
|
||||
|
||||
/**
|
||||
* Number of shards for the index {@link #indexName()}. Used for index creation. <br/>
|
||||
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of
|
||||
* Elasticsearch which changed to 1 as well in Elasticsearch 7.0.
|
||||
* ComposableAnnotationsUnitTest.documentAnnotationShouldBeComposable:60
|
||||
*
|
||||
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
|
||||
*/
|
||||
@Deprecated
|
||||
short shards() default 1;
|
||||
|
||||
/**
|
||||
* Number of replicas for the index {@link #indexName()}. Used for index creation.
|
||||
*
|
||||
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
|
||||
*/
|
||||
@Deprecated
|
||||
short replicas() default 1;
|
||||
|
||||
/**
|
||||
* Refresh interval for the index {@link #indexName()}. Used for index creation.
|
||||
*
|
||||
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
|
||||
*/
|
||||
@Deprecated
|
||||
String refreshInterval() default "1s";
|
||||
|
||||
/**
|
||||
* Index storage type for the index {@link #indexName()}. Used for index creation.
|
||||
*
|
||||
* @deprecated since 4.2, use the {@link Setting} annotation to configure settings
|
||||
*/
|
||||
@Deprecated
|
||||
String indexStoreType() default "fs";
|
||||
|
||||
/**
|
||||
* Configuration whether to create an index on repository bootstrapping.
|
||||
*/
|
||||
@@ -124,6 +81,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to set the dynamic mapping mode
|
||||
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.FIELD })
|
||||
@Documented
|
||||
@Deprecated
|
||||
public @interface DynamicMapping {
|
||||
|
||||
DynamicMappingValue value() default DynamicMappingValue.True;
|
||||
}
|
||||
@@ -53,9 +53,8 @@ public @interface Field {
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The <em>name</em> to be used to store the field inside the document.
|
||||
* <p>
|
||||
* √5 If not set, the name of the annotated property is used.
|
||||
* The <em>name</em> to be used to store the field inside the document. If not set, the name of the annotated property
|
||||
* is used.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Query
|
||||
@@ -34,16 +40,17 @@ import java.lang.annotation.*;
|
||||
public @interface Query {
|
||||
|
||||
/**
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
|
||||
*/
|
||||
@AliasFor("query")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Named Query Named looked up by repository.
|
||||
*
|
||||
* @deprecated since 4.2, not implemented and used anywhere
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
|
||||
* @since 5.0
|
||||
*/
|
||||
String name() default "";
|
||||
@AliasFor("value")
|
||||
String query() default "";
|
||||
|
||||
/**
|
||||
* Returns whether the query defined should be executed as count projection.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* This annotation can be placed on repository methods to define the properties that should be requested from
|
||||
* Elasticsearch when the method is run.
|
||||
*
|
||||
* @author Alexander Torres
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Documented
|
||||
public @interface SourceFilters {
|
||||
|
||||
/**
|
||||
* Properties to be requested from Elasticsearch to be included in the response. These can be passed in as literals
|
||||
* like
|
||||
*
|
||||
* <pre>
|
||||
* {@code @SourceFilters(includes = {"property1", "property2"})}
|
||||
* </pre>
|
||||
*
|
||||
* or as a parameterized value
|
||||
*
|
||||
* <pre>
|
||||
* {@code @SourceFilters(includes = "?0")}
|
||||
* </pre>
|
||||
*
|
||||
* when the list of properties is passed as a function parameter.
|
||||
*/
|
||||
String[] includes() default "";
|
||||
|
||||
/**
|
||||
* Properties to be requested from Elasticsearch to be excluded in the response. These can be passed in as literals
|
||||
* like
|
||||
*
|
||||
* <pre>
|
||||
* {@code @SourceFilters(excludes = {"property1", "property2"})}
|
||||
* </pre>
|
||||
*
|
||||
* or as a parameterized value
|
||||
*
|
||||
* <pre>
|
||||
* {@code @SourceFilters(excludes = "?0")}
|
||||
* </pre>
|
||||
*
|
||||
* when the list of properties is passed as a function parameter.
|
||||
*/
|
||||
String[] excludes() default "";
|
||||
}
|
||||
+5
-41
@@ -26,9 +26,7 @@ import java.util.function.Supplier;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -54,7 +52,6 @@ public interface ClientConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to localhost.
|
||||
* <p/>
|
||||
*
|
||||
* <pre class="code">
|
||||
* // "localhost:9200"
|
||||
@@ -69,9 +66,8 @@ public interface ClientConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}. For
|
||||
* example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration.create("localhost:9200");
|
||||
@@ -84,9 +80,8 @@ public interface ClientConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}. For
|
||||
* example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration
|
||||
@@ -173,14 +168,6 @@ public interface ClientConfiguration {
|
||||
*/
|
||||
Function<WebClient, WebClient> getWebClientConfigurer();
|
||||
|
||||
/**
|
||||
* @return the Rest Client configuration callback.
|
||||
* @since 4.2
|
||||
* @deprecated since 4.3 use {@link #getClientConfigurers()}
|
||||
*/
|
||||
@Deprecated
|
||||
HttpClientConfigCallback getHttpClientConfigurer();
|
||||
|
||||
/**
|
||||
* @return the client configuration callbacks
|
||||
* @since 4.3
|
||||
@@ -349,29 +336,6 @@ public interface ClientConfiguration {
|
||||
*/
|
||||
TerminalClientConfigurationBuilder withProxy(String proxy);
|
||||
|
||||
/**
|
||||
* set customization hook in case of a reactive configuration
|
||||
*
|
||||
* @param webClientConfigurer function to configure the WebClient
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
|
||||
* {@link ReactiveRestClients.WebClientConfigurationCallback}
|
||||
*/
|
||||
@Deprecated
|
||||
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
|
||||
|
||||
/**
|
||||
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
|
||||
*
|
||||
* @param httpClientConfigurer configuration callback, must not be null.
|
||||
* @return the {@link TerminalClientConfigurationBuilder}.
|
||||
* @since 4.2
|
||||
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
|
||||
* {@link RestClients.RestClientConfigurationCallback}
|
||||
*/
|
||||
@Deprecated
|
||||
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
|
||||
|
||||
/**
|
||||
* Register a {@link ClientConfigurationCallback} to configure the client.
|
||||
*
|
||||
|
||||
+7
-34
@@ -22,7 +22,6 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -31,8 +30,7 @@ import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -51,7 +49,7 @@ class ClientConfigurationBuilder
|
||||
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
|
||||
|
||||
private final List<InetSocketAddress> hosts = new ArrayList<>();
|
||||
private HttpHeaders headers = HttpHeaders.EMPTY;
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private boolean useSsl;
|
||||
private @Nullable SSLContext sslContext;
|
||||
private @Nullable HostnameVerifier hostnameVerifier;
|
||||
@@ -61,10 +59,10 @@ class ClientConfigurationBuilder
|
||||
private @Nullable String password;
|
||||
private @Nullable String pathPrefix;
|
||||
private @Nullable String proxy;
|
||||
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
|
||||
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
|
||||
@Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
|
||||
private List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
|
||||
private final Function<WebClient, WebClient> webClientConfigurer = Function.identity();
|
||||
private Supplier<HttpHeaders> headersSupplier = HttpHeaders::new;
|
||||
@Deprecated private final HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
|
||||
private final List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -75,7 +73,7 @@ class ClientConfigurationBuilder
|
||||
|
||||
Assert.notEmpty(hostAndPorts, "At least one host is required");
|
||||
|
||||
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList()));
|
||||
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -201,28 +199,6 @@ class ClientConfigurationBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withWebClientConfigurer(
|
||||
Function<WebClient, WebClient> webClientConfigurer) {
|
||||
|
||||
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
|
||||
|
||||
this.webClientConfigurer = webClientConfigurer;
|
||||
this.clientConfigurers.add(ReactiveRestClients.WebClientConfigurationCallback.from(webClientConfigurer));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer) {
|
||||
|
||||
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
|
||||
|
||||
this.httpClientConfigurer = httpClientConfigurer;
|
||||
this.clientConfigurers
|
||||
.add(RestClients.RestClientConfigurationCallback.from(httpClientConfigurer::customizeHttpClient));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminalClientConfigurationBuilder withClientConfigurer(
|
||||
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
|
||||
@@ -250,9 +226,6 @@ class ClientConfigurationBuilder
|
||||
public ClientConfiguration build() {
|
||||
|
||||
if (username != null && password != null) {
|
||||
if (HttpHeaders.EMPTY.equals(headers)) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
headers.setBasicAuth(username, password);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -29,11 +28,12 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Oliver Drotbohm
|
||||
* @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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +125,24 @@ public abstract class ClientLogger {
|
||||
* @param logId the correlation id, see {@link #newLogId()}.
|
||||
* @param statusCode the HTTP status code.
|
||||
*/
|
||||
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
|
||||
public static void logRawResponse(String logId, @Nullable Integer statusCode) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %s", logId, statusCode));
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received raw response: %d", logId, statusCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Integer statusCode, String headers) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%n%s", logId, statusCode, headers));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +153,27 @@ public abstract class ClientLogger {
|
||||
* @param statusCode the HTTP status code.
|
||||
* @param body body content.
|
||||
*/
|
||||
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
||||
public static void logResponse(String logId, Integer 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: %d%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 Integer statusCode, String headers, String body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nHeaders: %s%nResponse body: %s", logId, statusCode,
|
||||
headers, body));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-12
@@ -17,8 +17,6 @@ package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
@@ -28,7 +26,7 @@ import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -63,8 +61,8 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
|
||||
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
|
||||
|
||||
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
|
||||
this.headers = new HttpHeaders(headers);
|
||||
this.hosts = List.copyOf(hosts);
|
||||
this.headers = headers;
|
||||
this.useSsl = useSsl;
|
||||
this.sslContext = sslContext;
|
||||
this.soTimeout = soTimeout;
|
||||
@@ -129,13 +127,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
return webClientConfigurer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpClientConfigCallback getHttpClientConfigurer() {
|
||||
return httpClientConfigurer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
|
||||
return clientConfigurers;
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class InetSocketAddressParser {
|
||||
public class InetSocketAddressParser {
|
||||
|
||||
/**
|
||||
* Parse a host and port string into a {@link InetSocketAddress}.
|
||||
@@ -36,7 +36,7 @@ class InetSocketAddressParser {
|
||||
* @return a {@link InetSocketAddress} that is unresolved to avoid DNS lookups.
|
||||
* @see InetSocketAddress#createUnresolved(String, int)
|
||||
*/
|
||||
static InetSocketAddress parse(String hostPortString, int defaultPort) {
|
||||
public static InetSocketAddress parse(String hostPortString, int defaultPort) {
|
||||
|
||||
Assert.notNull(hostPortString, "HostPortString must not be null");
|
||||
String host;
|
||||
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class UnsupportedClientOperationException extends RuntimeException {
|
||||
public UnsupportedClientOperationException(Class<?> clientClass, String operation) {
|
||||
super("Client %1$s does not support the operation %2$s".formatted(clientClass, operation));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.Aggregate;
|
||||
|
||||
/**
|
||||
* Class to combine an Elasticsearch {@link co.elastic.clients.elasticsearch._types.aggregations.Aggregate} with its
|
||||
* name. Necessary as the Elasticsearch Aggregate does not know its name.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public class Aggregation {
|
||||
|
||||
private final String name;
|
||||
private final Aggregate aggregate;
|
||||
|
||||
public Aggregation(String name, Aggregate aggregate) {
|
||||
this.name = name;
|
||||
this.aggregate = aggregate;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Aggregate getAggregate() {
|
||||
return aggregate;
|
||||
}
|
||||
}
|
||||
+48
@@ -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,76 @@
|
||||
/*
|
||||
* 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 co.elastic.clients.transport.Transport;
|
||||
|
||||
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<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
|
||||
|
||||
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,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.cluster.ElasticsearchClusterClient;
|
||||
import co.elastic.clients.elasticsearch.cluster.HealthRequest;
|
||||
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
|
||||
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<ElasticsearchTransport, 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);
|
||||
}
|
||||
|
||||
}
|
||||
+337
@@ -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 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.Set;
|
||||
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()) {
|
||||
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
|
||||
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
|
||||
filterQueries.add(new Query(boolQueryBuilder.build()));
|
||||
} else if (chainedCriteria.isNegating()) {
|
||||
Collection<? extends Query> negatingFilters = buildNegatingFilter(criteria.getField().getName(),
|
||||
criteria.getFilterCriteriaEntries());
|
||||
filterQueries.addAll(negatingFilters);
|
||||
} 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(new Query(boolQuery));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<? extends Query> buildNegatingFilter(String fieldName,
|
||||
Set<Criteria.CriteriaEntry> filterCriteriaEntries) {
|
||||
|
||||
List<Query> negationFilters = new ArrayList<>();
|
||||
|
||||
filterCriteriaEntries.forEach(criteriaEntry -> {
|
||||
Optional<Query> query = queryFor(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName);
|
||||
|
||||
if (query.isPresent()) {
|
||||
BoolQuery negatingFilter = QueryBuilders.bool().mustNot(query.get()).build();
|
||||
negationFilters.add(new Query(negatingFilter));
|
||||
}
|
||||
});
|
||||
|
||||
return negationFilters;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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 loc) {
|
||||
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 topLeft) {
|
||||
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");
|
||||
case MILES -> sb.append("mi");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+9
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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.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 ? 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) {
|
||||
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) {
|
||||
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(escape(item.toString()));
|
||||
sb.append('"');
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String where those characters that TextParser expects to be escaped are escaped by a preceding
|
||||
* <code>\</code>. Copied from Apachae 2 licensed org.apache.lucene.queryparser.flexible.standard.QueryParserUtil
|
||||
* class
|
||||
*/
|
||||
public static String escape(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
// These characters are part of the query syntax and must be escaped
|
||||
if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':' || c == '^' || c == '['
|
||||
|| c == ']' || c == '\"' || c == '{' || c == '}' || c == '~' || c == '*' || c == '?' || c == '|' || c == '&'
|
||||
|| c == '/') {
|
||||
sb.append('\\');
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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.CompletionSuggestOption;
|
||||
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.function.Function;
|
||||
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, null,
|
||||
searchDocument -> null, jsonpMapper));
|
||||
});
|
||||
|
||||
NestedMetaData nestedMetaData = from(hit.nested());
|
||||
|
||||
Explanation explanation = from(hit.explanation());
|
||||
|
||||
List<String> matchedQueries = hit.matchedQueries();
|
||||
|
||||
Function<Map<String, JsonData>, EntityAsMap> fromFields = fields -> {
|
||||
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('}');
|
||||
return new EntityAsMap().fromJson(sb.toString());
|
||||
};
|
||||
|
||||
EntityAsMap hitFieldsAsMap = fromFields.apply(hit.fields());
|
||||
|
||||
Map<String, List<Object>> documentFields = new LinkedHashMap<>();
|
||||
hitFieldsAsMap.forEach((key, value) -> {
|
||||
if (value instanceof List) {
|
||||
// noinspection unchecked
|
||||
documentFields.put(key, (List<Object>) value);
|
||||
} else {
|
||||
documentFields.put(key, Collections.singletonList(value));
|
||||
}
|
||||
});
|
||||
|
||||
Document document;
|
||||
Object source = hit.source();
|
||||
if (source == null) {
|
||||
document = Document.from(hitFieldsAsMap);
|
||||
} else {
|
||||
if (source instanceof EntityAsMap) {
|
||||
document = Document.from((EntityAsMap) source);
|
||||
} else if (source instanceof JsonData jsonData) {
|
||||
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());
|
||||
}
|
||||
|
||||
public static SearchDocument from(CompletionSuggestOption<EntityAsMap> completionSuggestOption) {
|
||||
|
||||
Document document = completionSuggestOption.source() != null ? Document.from(completionSuggestOption.source())
|
||||
: Document.create();
|
||||
document.setIndex(completionSuggestOption.index());
|
||||
|
||||
if (completionSuggestOption.id() != null) {
|
||||
document.setId(completionSuggestOption.id());
|
||||
}
|
||||
|
||||
float score = completionSuggestOption.score() != null ? completionSuggestOption.score().floatValue() : Float.NaN;
|
||||
return new SearchDocumentAdapter(document, score, new Object[] {}, Collections.emptyMap(), Collections.emptyMap(),
|
||||
Collections.emptyMap(), null, null, null, completionSuggestOption.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());
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 org.springframework.data.elasticsearch.core.AggregationContainer;
|
||||
|
||||
/**
|
||||
* {@link AggregationContainer} for a {@link Aggregation} that holds Elasticsearch data.
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
|
||||
|
||||
private final Aggregation aggregation;
|
||||
|
||||
public ElasticsearchAggregation(Aggregation aggregation) {
|
||||
this.aggregation = aggregation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Aggregation aggregation() {
|
||||
return aggregation;
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* AggregationsContainer implementation for the Elasticsearch aggregations.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ElasticsearchAggregations implements AggregationsContainer<List<ElasticsearchAggregation>> {
|
||||
|
||||
private final List<ElasticsearchAggregation> aggregations;
|
||||
private final Map<String, ElasticsearchAggregation> aggregationsAsMap;
|
||||
|
||||
public ElasticsearchAggregations(Map<String, Aggregate> aggregations) {
|
||||
|
||||
Assert.notNull(aggregations, "aggregations must not be null");
|
||||
|
||||
aggregationsAsMap = new HashMap<>();
|
||||
aggregations.forEach((name, aggregate) -> aggregationsAsMap //
|
||||
.put(name, new ElasticsearchAggregation(new Aggregation(name, aggregate))));
|
||||
|
||||
this.aggregations = new ArrayList<>(aggregationsAsMap.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ElasticsearchAggregation> aggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ElasticsearchAggregation}s keyed by aggregation name.
|
||||
*/
|
||||
public Map<String, ElasticsearchAggregation> aggregationsAsMap() {
|
||||
return aggregationsAsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aggregation that is associated with the specified name.
|
||||
*
|
||||
* @param name the name of the aggregation
|
||||
* @return the aggregation or {@literal null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
public ElasticsearchAggregation get(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
return aggregationsAsMap.get(name);
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ElasticsearchClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ElasticsearchClientFactoryBean.class);
|
||||
setConfigurations(element, builder);
|
||||
return getSourcedBeanDefinition(builder, element, parserContext);
|
||||
}
|
||||
|
||||
private void setConfigurations(Element element, BeanDefinitionBuilder builder) {
|
||||
builder.addPropertyValue("hosts", element.getAttribute("hosts"));
|
||||
}
|
||||
|
||||
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
|
||||
ParserContext context) {
|
||||
AbstractBeanDefinition definition = builder.getBeanDefinition();
|
||||
definition.setSource(context.extractSource(source));
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.ElasticsearchClient;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* ElasticsearchClientFactoryBean
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ElasticsearchClientFactoryBean
|
||||
implements FactoryBean<ElasticsearchClient>, InitializingBean, DisposableBean {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ElasticsearchClientFactoryBean.class);
|
||||
|
||||
private @Nullable AutoCloseableElasticsearchClient client;
|
||||
private String hosts = "http://localhost:9200";
|
||||
static final String COMMA = ",";
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
LOGGER.info("Closing elasticSearch client");
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOGGER.error("Error closing ElasticSearch client: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
buildClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchClient getObject() {
|
||||
|
||||
if (client == null) {
|
||||
throw new FactoryBeanNotInitializedException();
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return ElasticsearchClient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void buildClient() throws Exception {
|
||||
|
||||
Assert.hasText(hosts, "[Assertion Failed] At least one host must be set.");
|
||||
|
||||
var clientConfiguration = ClientConfiguration.builder().connectedTo(hosts).build();
|
||||
client = (AutoCloseableElasticsearchClient) ElasticsearchClients.createImperative(clientConfiguration);
|
||||
}
|
||||
|
||||
public void setHosts(String hosts) {
|
||||
this.hosts = hosts;
|
||||
}
|
||||
|
||||
public String getHosts() {
|
||||
return this.hosts;
|
||||
}
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* 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.Version;
|
||||
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.entity.ContentType;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
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.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 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 ElasticsearchHttpClientConfigurationCallback restClientConfigurationCallback) {
|
||||
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
return clientBuilder;
|
||||
});
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurationCallback : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurationCallback instanceof ElasticsearchRestClientConfigurationCallback configurationCallback) {
|
||||
builder = configurationCallback.configure(builder);
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
// need to add the compatibility header, this is only done automatically when not passing in custom options.
|
||||
// code copied from RestClientTransport as it is not available outside the package
|
||||
ContentType jsonContentType = null;
|
||||
if (Version.VERSION == null) {
|
||||
jsonContentType = ContentType.APPLICATION_JSON;
|
||||
} else {
|
||||
jsonContentType = ContentType.create("application/vnd.elasticsearch+json",
|
||||
new BasicNameValuePair("compatible-with", String.valueOf(Version.VERSION.major())));
|
||||
}
|
||||
transportOptionsBuilder.addHeader("Accept", jsonContentType.toString());
|
||||
|
||||
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 entityRequest
|
||||
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
|
||||
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, 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.isEmpty()) {
|
||||
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the Elasticsearch RestClient's Http client with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
public interface ElasticsearchHttpClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
static ElasticsearchHttpClientConfigurationCallback from(
|
||||
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> httpClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(httpClientBuilderCallback, "httpClientBuilderCallback must not be null");
|
||||
|
||||
return httpClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
|
||||
* the RestClient client with a {@link RestClientBuilder}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface ElasticsearchRestClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<RestClientBuilder> {
|
||||
|
||||
static ElasticsearchRestClientConfigurationCallback from(
|
||||
Function<RestClientBuilder, RestClientBuilder> restClientBuilderCallback) {
|
||||
|
||||
Assert.notNull(restClientBuilderCallback, "restClientBuilderCallback must not be null");
|
||||
|
||||
return restClientBuilderCallback::apply;
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -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(name="elasticsearchClientConfiguration")
|
||||
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 elasticsearchRestClient(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);
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.elasticsearch.client.ResponseException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (ex instanceof ElasticsearchException elasticsearchException) {
|
||||
|
||||
ErrorResponse response = elasticsearchException.response();
|
||||
|
||||
if (response.status() == 404 && "index_not_found_exception".equals(response.error().type())) {
|
||||
|
||||
// noinspection RegExpRedundantEscape
|
||||
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
||||
String index = "";
|
||||
Matcher matcher = pattern.matcher(response.error().reason());
|
||||
if (matcher.matches()) {
|
||||
index = matcher.group(1);
|
||||
}
|
||||
return new NoSuchIndexException(index);
|
||||
}
|
||||
String body = JsonUtils.toJson(response, jsonpMapper);
|
||||
|
||||
if (response.error().type().contains("validation_exception")) {
|
||||
return new DataIntegrityViolationException(response.error().reason());
|
||||
}
|
||||
|
||||
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(Throwable exception) {
|
||||
Integer status = null;
|
||||
String message = null;
|
||||
|
||||
if (exception instanceof ResponseException responseException) {
|
||||
status = responseException.getResponse().getStatusLine().getStatusCode();
|
||||
message = responseException.getMessage();
|
||||
} else if (exception.getCause() != null) {
|
||||
return isSeqNoConflict(exception.getCause());
|
||||
}
|
||||
|
||||
if (status != null && message != null) {
|
||||
return status == 409 && message.contains("type\":\"version_conflict_engine_exception")
|
||||
&& message.contains("version conflict, required seqNo");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
/*
|
||||
* 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.TypeUtils.*;
|
||||
|
||||
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.elasticsearch.core.msearch.MultiSearchResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
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.Document;
|
||||
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 static final Log LOGGER = LogFactory.getLog(ElasticsearchTemplate.class);
|
||||
|
||||
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) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Assert.notNull(queries, "queries must not be null");
|
||||
Assert.notNull(bulkOptions, "bulkOptions must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
doBulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
|
||||
routingResolver.getRouting());
|
||||
co.elastic.clients.elasticsearch.core.UpdateResponse<Document> response = execute(
|
||||
client -> client.update(request, Document.class));
|
||||
return UpdateResponse.of(result(response.result()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByQueryResponse updateByQuery(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(updateQuery, "updateQuery must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
UpdateByQueryRequest request = requestConverter.documentUpdateByQueryRequest(updateQuery, index,
|
||||
getRefreshPolicy());
|
||||
|
||||
UpdateByQueryResponse byQueryResponse = execute(client -> client.updateByQuery(request));
|
||||
return responseConverter.byQueryResponse(byQueryResponse);
|
||||
}
|
||||
|
||||
@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) {
|
||||
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
|
||||
public String getClusterVersion() {
|
||||
return execute(client -> client.info().version().number());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVendor() {
|
||||
return "Elasticsearch";
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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
|
||||
public <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
|
||||
public <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> SearchScrollHits<T> getSearchScrollHits(Class<T> clazz, IndexCoordinates index,
|
||||
ResponseBody<EntityAsMap> 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
|
||||
public 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);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
|
||||
|
||||
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters);
|
||||
|
||||
MsearchResponse<EntityAsMap> msearchResponse = execute(client -> client.msearch(request, EntityAsMap.class));
|
||||
List<MultiSearchResponseItem<EntityAsMap>> responseItems = msearchResponse.responses();
|
||||
|
||||
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
|
||||
"number of response items does not match number of requests");
|
||||
|
||||
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
|
||||
|
||||
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
|
||||
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
|
||||
|
||||
while (queryIterator.hasNext()) {
|
||||
MultiSearchQueryParameter queryParameter = queryIterator.next();
|
||||
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
|
||||
|
||||
if (responseItem.isResult()) {
|
||||
|
||||
Class clazz = queryParameter.clazz;
|
||||
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
|
||||
queryParameter.index);
|
||||
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
|
||||
queryParameter.index);
|
||||
|
||||
SearchHits<?> searchHits = callback.doWith(
|
||||
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
|
||||
|
||||
searchHitsList.add(searchHits);
|
||||
} else {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER
|
||||
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchHitsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* value class combining the information needed for a single query in a multisearch request.
|
||||
*/
|
||||
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.notNull(keepAlive, "keepAlive must not be null");
|
||||
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
|
||||
|
||||
var request = requestConverter.searchOpenPointInTimeRequest(index, keepAlive, ignoreUnavailable);
|
||||
return execute(client -> client.openPointInTime(request)).id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean closePointInTime(String pit) {
|
||||
|
||||
Assert.notNull(pit, "pit must not be null");
|
||||
|
||||
ClosePointInTimeRequest request = requestConverter.searchClosePointInTime(pit);
|
||||
var response = execute(client -> client.closePointInTime(request));
|
||||
return response.succeeded();
|
||||
}
|
||||
|
||||
// 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> {}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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 (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.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,365 @@
|
||||
/*
|
||||
* 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.ElasticsearchTransport;
|
||||
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<ElasticsearchTransport, 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
|
||||
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) {
|
||||
|
||||
Assert.notNull(aliasNames, "aliasNames must not be null");
|
||||
|
||||
GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(aliasNames, null);
|
||||
var getAliasResponse = execute(client -> client.getAlias(getAliasRequest));
|
||||
return responseConverter.indicesGetAliasData(getAliasResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Set<AliasData>> getAliasesForIndex(String... indexNames) {
|
||||
|
||||
Assert.notNull(indexNames, "indexNames must not be null");
|
||||
|
||||
GetAliasRequest getAliasRequest = requestConverter.indicesGetAliasRequest(null, indexNames);
|
||||
var getAliasResponse = execute(client -> client.getAlias(getAliasRequest));
|
||||
return responseConverter.indicesGetAliasData(getAliasResponse);
|
||||
}
|
||||
|
||||
@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,70 @@
|
||||
/*
|
||||
* 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 java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @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 json = "{}";
|
||||
try {
|
||||
json = baos.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.warn("could not read json", e);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query, JsonpMapper mapper) {
|
||||
|
||||
if (query == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var baos = new ByteArrayOutputStream();
|
||||
var generator = mapper.jsonProvider().createGenerator(baos);
|
||||
query.serialize(generator, mapper);
|
||||
generator.close();
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.SortOptions;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
|
||||
import co.elastic.clients.elasticsearch.core.search.Suggester;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.elasticsearch.core.query.Query} implementation using query builders from the new
|
||||
* Elasticsearch Client library.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.4
|
||||
*/
|
||||
public class NativeQuery extends BaseQuery {
|
||||
|
||||
@Nullable private final Query query;
|
||||
@Nullable private Query filter;
|
||||
// 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;
|
||||
@Nullable private FieldCollapse fieldCollapse;
|
||||
private List<ScriptedField> scriptedFields = Collections.emptyList();
|
||||
private List<SortOptions> sortOptions = Collections.emptyList();
|
||||
|
||||
public NativeQuery(NativeQueryBuilder builder) {
|
||||
super(builder);
|
||||
this.query = builder.getQuery();
|
||||
this.filter = builder.getFilter();
|
||||
this.aggregations.putAll(builder.getAggregations());
|
||||
this.suggester = builder.getSuggester();
|
||||
this.fieldCollapse = builder.getFieldCollapse();
|
||||
this.scriptedFields = builder.getScriptedFields();
|
||||
this.sortOptions = builder.getSortOptions();
|
||||
}
|
||||
|
||||
public NativeQuery(@Nullable Query query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public static NativeQueryBuilder builder() {
|
||||
return new NativeQueryBuilder();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Query getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
public Map<String, Aggregation> getAggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Suggester getSuggester() {
|
||||
return suggester;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldCollapse getFieldCollapse() {
|
||||
return fieldCollapse;
|
||||
}
|
||||
|
||||
public List<ScriptedField> getScriptedFields() {
|
||||
return scriptedFields;
|
||||
}
|
||||
|
||||
public List<SortOptions> getSortOptions() {
|
||||
return sortOptions;
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.SortOptions;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||
import co.elastic.clients.elasticsearch.core.search.FieldCollapse;
|
||||
import co.elastic.clients.elasticsearch.core.search.Suggester;
|
||||
import co.elastic.clients.util.ObjectBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.4
|
||||
*/
|
||||
public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQueryBuilder> {
|
||||
|
||||
@Nullable private Query query;
|
||||
@Nullable private Query filter;
|
||||
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
|
||||
@Nullable private Suggester suggester;
|
||||
@Nullable private FieldCollapse fieldCollapse;
|
||||
private final List<ScriptedField> scriptedFields = new ArrayList<>();
|
||||
private List<SortOptions> sortOptions = new ArrayList<>();
|
||||
|
||||
public NativeQueryBuilder() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Query getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public Map<String, Aggregation> getAggregations() {
|
||||
return aggregations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Suggester getSuggester() {
|
||||
return suggester;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldCollapse getFieldCollapse() {
|
||||
return fieldCollapse;
|
||||
}
|
||||
|
||||
public List<ScriptedField> getScriptedFields() {
|
||||
return scriptedFields;
|
||||
}
|
||||
|
||||
public List<SortOptions> getSortOptions() {
|
||||
return sortOptions;
|
||||
}
|
||||
|
||||
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 withFilter(@Nullable Query filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQueryBuilder withFilter(Function<Query.Builder, ObjectBuilder<Query>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return withFilter(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 NativeQueryBuilder withFieldCollapse(@Nullable FieldCollapse fieldCollapse) {
|
||||
this.fieldCollapse = fieldCollapse;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQueryBuilder withScriptedField(ScriptedField scriptedField) {
|
||||
|
||||
Assert.notNull(scriptedField, "scriptedField must not be null");
|
||||
|
||||
this.scriptedFields.add(scriptedField);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQueryBuilder withSort(List<SortOptions> values) {
|
||||
|
||||
Assert.notEmpty(values, "values must not be empty");
|
||||
sortOptions.clear();
|
||||
sortOptions.addAll(values);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQueryBuilder withSort(SortOptions value, SortOptions... values) {
|
||||
|
||||
Assert.notNull(value, "value must not be null");
|
||||
sortOptions.add(value);
|
||||
if (values.length > 0) {
|
||||
sortOptions.addAll(Arrays.asList(values));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQueryBuilder withSort(Function<SortOptions.Builder, ObjectBuilder<SortOptions>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
withSort(fn.apply(new SortOptions.Builder()).build());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeQuery build() {
|
||||
return new NativeQuery(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.IdsQuery;
|
||||
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.List;
|
||||
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 IdsQuery idsQuery(List<String> ids) {
|
||||
|
||||
Assert.notNull(ids, "ids must not be null");
|
||||
|
||||
return IdsQuery.of(i -> i.values(ids));
|
||||
}
|
||||
|
||||
public static Query idsQueryAsQuery(List<String> ids) {
|
||||
|
||||
Assert.notNull(ids, "ids must not be null");
|
||||
|
||||
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.ids(idsQuery(ids));
|
||||
|
||||
return builder.apply(new Query.Builder()).build();
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
+71
@@ -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 co.elastic.clients.transport.Transport;
|
||||
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<T extends Transport, CLIENT extends ApiClient<T, CLIENT>> {
|
||||
protected final CLIENT client;
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
protected final RequestConverter requestConverter;
|
||||
protected final ResponseConverter responseConverter;
|
||||
protected final ElasticsearchExceptionTranslator exceptionTranslator;
|
||||
|
||||
public ReactiveChildTemplate(CLIENT client, ElasticsearchConverter elasticsearchConverter) {
|
||||
this.client = client;
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
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 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);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -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.cluster.HealthRequest;
|
||||
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.erhlc.ReactiveClusterOperations;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ReactiveClusterTemplate
|
||||
extends ReactiveChildTemplate<ElasticsearchTransport, 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);
|
||||
}
|
||||
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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.elasticsearch.core.search.ResponseBody;
|
||||
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, P> Mono<UpdateResponse<T>> update(UpdateRequest<T, P> request, Class<T> clazz) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
// noinspection unchecked
|
||||
JsonEndpoint<UpdateRequest<?, ?>, UpdateResponse<T>, ErrorResponse> endpoint = new EndpointWithResponseMapperAttr(
|
||||
UpdateRequest._ENDPOINT, "co.elastic.clients:Deserializer:_global.update.TDocument",
|
||||
this.getDeserializer(clazz));
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, this.transportOptions));
|
||||
}
|
||||
|
||||
public <T, P> Mono<UpdateResponse<T>> update(
|
||||
Function<UpdateRequest.Builder<T, P>, ObjectBuilder<UpdateRequest<T, P>>> fn, Class<T> clazz) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return update(fn.apply(new UpdateRequest.Builder<>()).build(), clazz);
|
||||
}
|
||||
|
||||
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 <T> Mono<MgetResponse<T>> mget(MgetRequest request, Class<T> clazz) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
// noinspection unchecked
|
||||
JsonEndpoint<MgetRequest, MgetResponse<T>, ErrorResponse> endpoint = (JsonEndpoint<MgetRequest, MgetResponse<T>, ErrorResponse>) MgetRequest._ENDPOINT;
|
||||
endpoint = new EndpointWithResponseMapperAttr<>(endpoint, "co.elastic.clients:Deserializer:_global.mget.TDocument",
|
||||
this.getDeserializer(clazz));
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, endpoint, transportOptions));
|
||||
}
|
||||
|
||||
public <T> Mono<MgetResponse<T>> mget(Function<MgetRequest.Builder, ObjectBuilder<MgetRequest>> fn, Class<T> clazz) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return mget(fn.apply(new MgetRequest.Builder()).build(), clazz);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public Mono<DeleteByQueryResponse> deleteByQuery(DeleteByQueryRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, DeleteByQueryRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
public Mono<DeleteByQueryResponse> deleteByQuery(
|
||||
Function<DeleteByQueryRequest.Builder, ObjectBuilder<DeleteByQueryRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return deleteByQuery(fn.apply(new DeleteByQueryRequest.Builder()).build());
|
||||
}
|
||||
|
||||
// endregion
|
||||
// region search
|
||||
|
||||
public <T> Mono<ResponseBody<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<ResponseBody<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());
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.0
|
||||
*/
|
||||
public Mono<OpenPointInTimeResponse> openPointInTime(OpenPointInTimeRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, OpenPointInTimeRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.0
|
||||
*/
|
||||
public Mono<OpenPointInTimeResponse> openPointInTime(
|
||||
Function<OpenPointInTimeRequest.Builder, ObjectBuilder<OpenPointInTimeRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return openPointInTime(fn.apply(new OpenPointInTimeRequest.Builder()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.0
|
||||
*/
|
||||
public Mono<ClosePointInTimeResponse> closePointInTime(ClosePointInTimeRequest request) {
|
||||
|
||||
Assert.notNull(request, "request must not be null");
|
||||
|
||||
return Mono.fromFuture(transport.performRequestAsync(request, ClosePointInTimeRequest._ENDPOINT, transportOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.0
|
||||
*/
|
||||
public Mono<ClosePointInTimeResponse> closePointInTime(
|
||||
Function<ClosePointInTimeRequest.Builder, ObjectBuilder<ClosePointInTimeRequest>> fn) {
|
||||
|
||||
Assert.notNull(fn, "fn must not be null");
|
||||
|
||||
return closePointInTime(fn.apply(new ClosePointInTimeRequest.Builder()).build());
|
||||
}
|
||||
// endregion
|
||||
|
||||
}
|
||||
+56
@@ -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());
|
||||
}
|
||||
}
|
||||
+97
@@ -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(name="elasticsearchClientConfiguration")
|
||||
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 elasticsearchRestClient(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();
|
||||
}
|
||||
}
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* 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<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);
|
||||
}
|
||||
|
||||
@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<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<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<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);
|
||||
}
|
||||
|
||||
}
|
||||
+560
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* 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 co.elastic.clients.util.ApiTypeHelper.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
|
||||
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.elasticsearch.core.get.GetResult;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
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.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.erhlc.ReactiveClusterOperations;
|
||||
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.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocument;
|
||||
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
|
||||
import org.springframework.data.elasticsearch.core.mapping.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;
|
||||
|
||||
public 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> 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> 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(
|
||||
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 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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<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);
|
||||
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
|
||||
|
||||
Publisher<MgetResponse<EntityAsMap>> response = execute(
|
||||
(ClientCallback<Publisher<MgetResponse<EntityAsMap>>>) client -> client.mget(request, EntityAsMap.class));
|
||||
|
||||
return Mono.from(response)//
|
||||
.flatMapMany(it -> Flux.fromIterable(DocumentAdapters.from(it))) //
|
||||
.flatMap(multiGetItem -> {
|
||||
if (multiGetItem.isFailed()) {
|
||||
return Mono.just(MultiGetItem.of(null, multiGetItem.getFailure()));
|
||||
} else {
|
||||
return callback.toEntity(multiGetItem.getItem()) //
|
||||
.map(t -> MultiGetItem.of(t, multiGetItem.getFailure()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
protected ReactiveElasticsearchTemplate doCopy() {
|
||||
return new ReactiveElasticsearchTemplate(client, converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Boolean> doExists(String id, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(id, "id must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index, true);
|
||||
|
||||
return Mono.from(execute(
|
||||
((ClientCallback<Publisher<GetResponse<EntityAsMap>>>) client -> client.get(getRequest, EntityAsMap.class))))
|
||||
.map(GetResult::found) //
|
||||
.onErrorReturn(NoSuchIndexException.class, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, entityType, index,
|
||||
getRefreshPolicy());
|
||||
return Mono
|
||||
.from(execute((ClientCallback<Publisher<DeleteByQueryResponse>>) client -> client.deleteByQuery(request)))
|
||||
.map(responseConverter::byQueryResponse);
|
||||
}
|
||||
|
||||
// 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<ResponseBody<EntityAsMap>> searchResponses = Flux.usingWhen(Mono.fromSupplier(ScrollState::new), //
|
||||
state -> Mono
|
||||
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client1 -> client1
|
||||
.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>>>) client1 -> {
|
||||
ScrollRequest scrollRequest = ScrollRequest
|
||||
.of(sr -> sr.scrollId(state.getScrollId()).scroll(scrollTimeout));
|
||||
return client1.scroll(scrollRequest, 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<ResponseBody<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<ResponseBody<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<>((Class<T>) clazz, index);
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> callback.toEntity(searchDocument)
|
||||
.toFuture();
|
||||
|
||||
return Mono
|
||||
.from(execute((ClientCallback<Publisher<ResponseBody<EntityAsMap>>>) client -> client.search(searchRequest,
|
||||
EntityAsMap.class)))
|
||||
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator, jsonpMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<? extends AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
return doFindForResponse(query, entityType, index).flatMapMany(searchDocumentResponse -> {
|
||||
ElasticsearchAggregations aggregations = (ElasticsearchAggregations) searchDocumentResponse.getAggregations();
|
||||
return aggregations == null ? Flux.empty() : Flux.fromIterable(aggregations.aggregations());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
|
||||
|
||||
Assert.notNull(index, "index must not be null");
|
||||
Assert.notNull(keepAlive, "keepAlive must not be null");
|
||||
Assert.notNull(ignoreUnavailable, "ignoreUnavailable must not be null");
|
||||
|
||||
var request = requestConverter.searchOpenPointInTimeRequest(index, keepAlive, ignoreUnavailable);
|
||||
return Mono
|
||||
.from(execute((ClientCallback<Publisher<OpenPointInTimeResponse>>) client -> client.openPointInTime(request)))
|
||||
.map(OpenPointInTimeResponse::id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> closePointInTime(String pit) {
|
||||
|
||||
Assert.notNull(pit, "pit must not be null");
|
||||
|
||||
ClosePointInTimeRequest request = requestConverter.searchClosePointInTime(pit);
|
||||
return Mono
|
||||
.from(execute((ClientCallback<Publisher<ClosePointInTimeResponse>>) client -> client.closePointInTime(request)))
|
||||
.map(ClosePointInTimeResponse::succeeded);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
public Mono<String> getVendor() {
|
||||
return Mono.just("Elasticsearch");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getRuntimeLibraryVersion() {
|
||||
return Mono.just(Version.VERSION != null ? Version.VERSION.toString() : "null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> getClusterVersion() {
|
||||
return Mono.from(execute((ReactiveElasticsearchClient reactiveElasticsearchClient) -> {
|
||||
try (var ignored = DANGEROUS_disableRequiredPropertiesCheck(true)) {
|
||||
return reactiveElasticsearchClient.info();
|
||||
}
|
||||
})).map(infoResponse -> infoResponse.version().number());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UpdateResponse> update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(updateQuery, "UpdateQuery must not be null");
|
||||
Assert.notNull(index, "Index must not be null");
|
||||
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
|
||||
routingResolver.getRouting());
|
||||
|
||||
return Mono.from(execute(
|
||||
(ClientCallback<Publisher<co.elastic.clients.elasticsearch.core.UpdateResponse<Document>>>) client -> client
|
||||
.update(request, Document.class)))
|
||||
.flatMap(response -> {
|
||||
UpdateResponse.Result result = result(response.result());
|
||||
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
|
||||
});
|
||||
}
|
||||
|
||||
@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 Query matchAllQuery() {
|
||||
return NativeQuery.builder().withQuery(QueryBuilders.matchAllQueryAsQuery()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query idsQuery(List<String> ids) {
|
||||
return NativeQuery.builder().withQuery(QueryBuilders.idsQueryAsQuery(ids)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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.ElasticsearchTransport;
|
||||
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<ElasticsearchTransport, 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(ReactiveElasticsearchIndicesClient::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(client -> client.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(client -> client.deleteTemplate(deleteTemplateRequestES)))
|
||||
.map(DeleteTemplateResponse::acknowledged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<IndexInformation> getInformation(IndexCoordinates index) {
|
||||
|
||||
GetIndexRequest request = requestConverter.indicesGetIndexRequest(index);
|
||||
|
||||
return Mono.from(execute(client -> client.get(request))) //
|
||||
.map(responseConverter::indicesGetIndexInformations) //
|
||||
.flatMapMany(Flux::fromIterable);
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
}
|
||||
+1641
File diff suppressed because it is too large
Load Diff
+440
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* 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.UpdateByQueryResponse;
|
||||
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(healthResponse.taskMaxWaitingInQueueMillis().toEpochMilli()) //
|
||||
.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(reindexResponse.throttledMillis().toEpochMilli()) //
|
||||
.withRequestsPerSecond(reindexResponse.requestsPerSecond()) //
|
||||
.withThrottledUntilMillis(reindexResponse.throttledUntilMillis().toEpochMilli()).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) {
|
||||
// the code for the methods taking a DeleteByQueryResponse or a UpdateByQueryResponse is duplicated because the
|
||||
// Elasticsearch responses do not share a common class
|
||||
// noinspection DuplicatedCode
|
||||
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();
|
||||
}
|
||||
|
||||
public ByQueryResponse byQueryResponse(UpdateByQueryResponse response) {
|
||||
// the code for the methods taking a DeleteByQueryResponse or a UpdateByQueryResponse is duplicated because the
|
||||
// Elasticsearch responses do not share a common class
|
||||
// noinspection DuplicatedCode
|
||||
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
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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.CompletionSuggest;
|
||||
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
|
||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
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.CompletionSuggestion;
|
||||
import org.springframework.data.elasticsearch.core.suggest.response.PhraseSuggestion;
|
||||
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;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Factory class to create {@link SearchDocumentResponse} instances.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
class SearchDocumentResponseBuilder {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponseBuilder.class);
|
||||
|
||||
/**
|
||||
* creates a SearchDocumentResponse from the {@link SearchResponse}
|
||||
*
|
||||
* @param responseBody the Elasticsearch response body
|
||||
* @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(ResponseBody<EntityAsMap> responseBody,
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator, JsonpMapper jsonpMapper) {
|
||||
|
||||
Assert.notNull(responseBody, "responseBody must not be null");
|
||||
Assert.notNull(entityCreator, "entityCreator must not be null");
|
||||
|
||||
HitsMetadata<EntityAsMap> hitsMetadata = responseBody.hits();
|
||||
String scrollId = responseBody.scrollId();
|
||||
Map<String, Aggregate> aggregations = responseBody.aggregations();
|
||||
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
|
||||
var pointInTimeId = responseBody.pitId();
|
||||
|
||||
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a {@link SearchDocumentResponseBuilder} from {@link HitsMetadata} with the given scrollId aggregations and
|
||||
* suggestES
|
||||
*
|
||||
* @param <T> entity type
|
||||
* @param hitsMetadata the {@link HitsMetadata} 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,
|
||||
@Nullable String pointInTimeId, @Nullable 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();
|
||||
totalHitsRelation = switch (responseTotalHits.relation().jsonValue()) {
|
||||
case "eq" -> TotalHitsRelation.EQUAL_TO.name();
|
||||
case "gte" -> TotalHitsRelation.GREATER_THAN_OR_EQUAL_TO.name();
|
||||
default -> 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;
|
||||
|
||||
Suggest suggest = suggestFrom(suggestES, entityCreator);
|
||||
|
||||
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
|
||||
aggregationsContainer, suggest);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> Suggest suggestFrom(Map<String, List<Suggestion<EntityAsMap>>> suggestES,
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||
|
||||
if (CollectionUtils.isEmpty(suggestES)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>>> suggestions = new ArrayList<>();
|
||||
|
||||
suggestES.forEach((name, suggestionsES) -> {
|
||||
|
||||
if (!suggestionsES.isEmpty()) {
|
||||
// take the type from the first entry
|
||||
switch (suggestionsES.get(0)._kind()) {
|
||||
case Term -> {
|
||||
suggestions.add(getTermSuggestion(name, suggestionsES));
|
||||
break;
|
||||
}
|
||||
case Phrase -> {
|
||||
suggestions.add(getPhraseSuggestion(name, suggestionsES));
|
||||
break;
|
||||
}
|
||||
case Completion -> {
|
||||
suggestions.add(getCompletionSuggestion(name, suggestionsES, entityCreator));
|
||||
break;
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// todo: hasScoreDocs checks if any one
|
||||
boolean hasScoreDocs = false;
|
||||
|
||||
return new Suggest(suggestions, hasScoreDocs);
|
||||
}
|
||||
|
||||
private static TermSuggestion getTermSuggestion(String name, List<Suggestion<EntityAsMap>> suggestionsES) {
|
||||
|
||||
List<TermSuggestion.Entry> entries = new ArrayList<>();
|
||||
suggestionsES.forEach(suggestionES -> {
|
||||
var termSuggest = suggestionES.term();
|
||||
var termSuggestOptions = termSuggest.options();
|
||||
List<TermSuggestion.Entry.Option> options = new ArrayList<>();
|
||||
termSuggestOptions.forEach(optionES -> options.add(new TermSuggestion.Entry.Option(optionES.text(), null,
|
||||
optionES.score(), null, Math.toIntExact(optionES.freq()))));
|
||||
entries.add(new TermSuggestion.Entry(termSuggest.text(), termSuggest.offset(), termSuggest.length(), options));
|
||||
});
|
||||
return new TermSuggestion(name, suggestionsES.size(), entries, null);
|
||||
}
|
||||
|
||||
private static PhraseSuggestion getPhraseSuggestion(String name, List<Suggestion<EntityAsMap>> suggestionsES) {
|
||||
|
||||
List<PhraseSuggestion.Entry> entries = new ArrayList<>();
|
||||
suggestionsES.forEach(suggestionES -> {
|
||||
var phraseSuggest = suggestionES.phrase();
|
||||
var phraseSuggestOptions = phraseSuggest.options();
|
||||
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
|
||||
phraseSuggestOptions.forEach(optionES -> options
|
||||
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), null, null)));
|
||||
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
|
||||
options, null));
|
||||
});
|
||||
return new PhraseSuggestion(name, suggestionsES.size(), entries);
|
||||
}
|
||||
|
||||
private static <T> CompletionSuggestion<T> getCompletionSuggestion(String name,
|
||||
List<Suggestion<EntityAsMap>> suggestionsES, SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||
List<CompletionSuggestion.Entry<T>> entries = new ArrayList<>();
|
||||
suggestionsES.forEach(suggestionES -> {
|
||||
CompletionSuggest<EntityAsMap> completionSuggest = suggestionES.completion();
|
||||
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
|
||||
List<CompletionSuggestOption<EntityAsMap>> optionsES = completionSuggest.options();
|
||||
optionsES.forEach(optionES -> {
|
||||
SearchDocument searchDocument = (optionES.source() != null) ? DocumentAdapters.from(optionES) : 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: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Set<String>> contexts = new HashMap<>();
|
||||
optionES.contexts().forEach((key, contextList) -> contexts.put(key,
|
||||
contextList.stream().map(context -> context._get().toString()).collect(Collectors.toSet())));
|
||||
|
||||
// response from the new client does not have a doc and shardindex as the ScoreDoc from the old client responses
|
||||
|
||||
options.add(new CompletionSuggestion.Entry.Option<>(optionES.text(), null, optionES.score(),
|
||||
optionES.collateMatch() != null ? optionES.collateMatch() : false, contexts,
|
||||
new ScoreDoc(optionES.score() != null ? optionES.score() : Double.NaN, null, null), searchDocument,
|
||||
hitEntity));
|
||||
});
|
||||
|
||||
entries.add(new CompletionSuggestion.Entry<>(completionSuggest.text(), completionSuggest.offset(),
|
||||
completionSuggest.length(), options));
|
||||
});
|
||||
return new CompletionSuggestion<>(name, suggestionsES.size(), entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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.*;
|
||||
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
|
||||
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
|
||||
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 co.elastic.clients.elasticsearch.core.search.ScoreMode;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
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.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
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) {
|
||||
return switch (value.toLowerCase()) {
|
||||
case "chars" -> BoundaryScanner.Chars;
|
||||
case "sentence" -> BoundaryScanner.Sentence;
|
||||
case "word" -> BoundaryScanner.Word;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Conflicts conflicts(ReindexRequest.Conflicts conflicts) {
|
||||
return switch (conflicts) {
|
||||
case ABORT -> Conflicts.Abort;
|
||||
case PROCEED -> Conflicts.Proceed;
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static DistanceUnit distanceUnit(String unit) {
|
||||
|
||||
return switch (unit.toLowerCase()) {
|
||||
case "in", "inch" -> DistanceUnit.Inches;
|
||||
case "yd", "yards" -> DistanceUnit.Yards;
|
||||
case "ft", "feet" -> DistanceUnit.Feet;
|
||||
case "km", "kilometers" -> DistanceUnit.Kilometers;
|
||||
case "nm", "nmi" -> DistanceUnit.NauticMiles;
|
||||
case "mm", "millimeters" -> DistanceUnit.Millimeters;
|
||||
case "cm", "centimeters" -> DistanceUnit.Centimeters;
|
||||
case "mi", "miles" -> DistanceUnit.Miles;
|
||||
case "m", "meters" -> DistanceUnit.Meters;
|
||||
default -> 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) {
|
||||
|
||||
return switch (distanceType) {
|
||||
case arc -> GeoDistanceType.Arc;
|
||||
case plane -> GeoDistanceType.Plane;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static HighlighterFragmenter highlighterFragmenter(@Nullable String value) {
|
||||
|
||||
if (value != null) {
|
||||
return switch (value.toLowerCase()) {
|
||||
case "simple" -> HighlighterFragmenter.Simple;
|
||||
case "span" -> HighlighterFragmenter.Span;
|
||||
default -> 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) {
|
||||
return switch (value.toLowerCase()) {
|
||||
case "unified" -> HighlighterType.Unified;
|
||||
case "plain" -> HighlighterType.Plain;
|
||||
case "fvh" -> HighlighterType.FastVector;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static HighlighterEncoder highlighterEncoder(@Nullable String value) {
|
||||
|
||||
if (value != null) {
|
||||
return switch (value.toLowerCase()) {
|
||||
case "default" -> HighlighterEncoder.Default;
|
||||
case "html" -> HighlighterEncoder.Html;
|
||||
default -> 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) {
|
||||
return switch (opType) {
|
||||
case INDEX -> OpType.Index;
|
||||
case CREATE -> OpType.Create;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Refresh refresh(@Nullable RefreshPolicy refreshPolicy) {
|
||||
|
||||
if (refreshPolicy == null) {
|
||||
return Refresh.False;
|
||||
}
|
||||
|
||||
return switch (refreshPolicy) {
|
||||
case IMMEDIATE -> Refresh.True;
|
||||
case WAIT_UNTIL -> Refresh.WaitFor;
|
||||
case NONE -> Refresh.False;
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static UpdateResponse.Result result(@Nullable Result result) {
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (result) {
|
||||
case Created -> UpdateResponse.Result.CREATED;
|
||||
case Updated -> UpdateResponse.Result.UPDATED;
|
||||
case Deleted -> UpdateResponse.Result.DELETED;
|
||||
case NotFound -> UpdateResponse.Result.NOT_FOUND;
|
||||
case NoOp -> UpdateResponse.Result.NOOP;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ScoreMode scoreMode(@Nullable RescorerQuery.ScoreMode scoreMode) {
|
||||
|
||||
if (scoreMode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (scoreMode) {
|
||||
case Default -> null;
|
||||
case Avg -> ScoreMode.Avg;
|
||||
case Max -> ScoreMode.Max;
|
||||
case Min -> ScoreMode.Min;
|
||||
case Total -> ScoreMode.Total;
|
||||
case Multiply -> ScoreMode.Multiply;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static SearchType searchType(@Nullable Query.SearchType searchType) {
|
||||
|
||||
if (searchType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (searchType) {
|
||||
case QUERY_THEN_FETCH -> SearchType.QueryThenFetch;
|
||||
case DFS_QUERY_THEN_FETCH -> SearchType.DfsQueryThenFetch;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Slices slices(@Nullable Long count) {
|
||||
|
||||
if (count == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Slices.of(s -> s.value(Math.toIntExact(count)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static SortMode sortMode(Order.Mode mode) {
|
||||
|
||||
return switch (mode) {
|
||||
case min -> SortMode.Min;
|
||||
case max -> SortMode.Max;
|
||||
case median -> SortMode.Median;
|
||||
case avg -> SortMode.Avg;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Time time(@Nullable Duration duration) {
|
||||
|
||||
if (duration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Time.of(t -> t.time(duration.toMillis() + "ms"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String timeStringMs(@Nullable Duration duration) {
|
||||
|
||||
if (duration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return duration.toMillis() + "ms";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static VersionType versionType(
|
||||
@Nullable org.springframework.data.elasticsearch.annotations.Document.VersionType versionType) {
|
||||
|
||||
if (versionType != null) {
|
||||
return switch (versionType) {
|
||||
case INTERNAL -> VersionType.Internal;
|
||||
case EXTERNAL -> VersionType.External;
|
||||
case EXTERNAL_GTE -> VersionType.ExternalGte;
|
||||
case FORCE -> 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;
|
||||
+4
-2
@@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.config;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,9 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @see ElasticsearchConfigurationSupport
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
+4
-3
@@ -13,13 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.config;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -29,7 +28,9 @@ import org.springframework.lang.Nullable;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @see ElasticsearchConfigurationSupport
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
+22
-26
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
|
||||
|
||||
@@ -47,7 +47,9 @@ import org.springframework.util.Assert;
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class CriteriaFilterProcessor {
|
||||
|
||||
@Nullable
|
||||
@@ -103,37 +105,37 @@ class CriteriaFilterProcessor {
|
||||
QueryBuilder filter = null;
|
||||
|
||||
switch (key) {
|
||||
case WITHIN:
|
||||
case WITHIN -> {
|
||||
Assert.isTrue(value instanceof Object[], "Value of a geo distance filter should be an array of two values.");
|
||||
filter = withinQuery(fieldName, (Object[]) value);
|
||||
break;
|
||||
case BBOX:
|
||||
}
|
||||
case BBOX -> {
|
||||
Assert.isTrue(value instanceof Object[],
|
||||
"Value of a boundedBy filter should be an array of one or two values.");
|
||||
filter = boundingBoxQuery(fieldName, (Object[]) value);
|
||||
break;
|
||||
case GEO_INTERSECTS:
|
||||
}
|
||||
case GEO_INTERSECTS -> {
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_INTERSECTS filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "intersects");
|
||||
break;
|
||||
case GEO_IS_DISJOINT:
|
||||
}
|
||||
case GEO_IS_DISJOINT -> {
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_IS_DISJOINT filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "disjoint");
|
||||
break;
|
||||
case GEO_WITHIN:
|
||||
}
|
||||
case GEO_WITHIN -> {
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_WITHIN filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "within");
|
||||
break;
|
||||
case GEO_CONTAINS:
|
||||
}
|
||||
case GEO_CONTAINS -> {
|
||||
Assert.isTrue(value instanceof GeoJson<?>, "value of a GEO_CONTAINS filter must be a GeoJson object");
|
||||
filter = geoJsonQuery(fieldName, (GeoJson<?>) value, "contains");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private QueryBuilder withinQuery(String fieldName, Object[] valArray) {
|
||||
private QueryBuilder withinQuery(String fieldName, Object... valArray) {
|
||||
|
||||
GeoDistanceQueryBuilder filter = QueryBuilders.geoDistanceQuery(fieldName);
|
||||
|
||||
@@ -152,8 +154,7 @@ class CriteriaFilterProcessor {
|
||||
dist.append((String) valArray[1]);
|
||||
}
|
||||
|
||||
if (valArray[0] instanceof GeoPoint) {
|
||||
GeoPoint loc = (GeoPoint) valArray[0];
|
||||
if (valArray[0]instanceof GeoPoint loc) {
|
||||
filter.point(loc.getLat(), loc.getLon()).distance(dist.toString()).geoDistance(GeoDistance.PLANE);
|
||||
} else if (valArray[0] instanceof Point) {
|
||||
GeoPoint loc = GeoPoint.fromPoint((Point) valArray[0]);
|
||||
@@ -172,7 +173,7 @@ class CriteriaFilterProcessor {
|
||||
return filter;
|
||||
}
|
||||
|
||||
private QueryBuilder boundingBoxQuery(String fieldName, Object[] valArray) {
|
||||
private QueryBuilder boundingBoxQuery(String fieldName, Object... valArray) {
|
||||
|
||||
Assert.noNullElements(valArray, "Geo boundedBy filter takes a not null element array as parameter.");
|
||||
|
||||
@@ -215,12 +216,8 @@ class CriteriaFilterProcessor {
|
||||
Metrics metric = (Metrics) distance.getMetric();
|
||||
|
||||
switch (metric) {
|
||||
case KILOMETERS:
|
||||
sb.append("km");
|
||||
break;
|
||||
case MILES:
|
||||
sb.append("mi");
|
||||
break;
|
||||
case KILOMETERS -> sb.append("km");
|
||||
case MILES -> sb.append("mi");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,11 +245,10 @@ class CriteriaFilterProcessor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object[] values) {
|
||||
private void twoParameterBBox(GeoBoundingBoxQueryBuilder filter, Object... values) {
|
||||
Assert.isTrue(isType(values, GeoPoint.class) || isType(values, String.class),
|
||||
" both elements of boundedBy filter must be type of GeoPoint or text(format lat,lon or geohash)");
|
||||
if (values[0] instanceof GeoPoint) {
|
||||
GeoPoint topLeft = (GeoPoint) values[0];
|
||||
if (values[0]instanceof GeoPoint topLeft) {
|
||||
GeoPoint bottomRight = (GeoPoint) values[1];
|
||||
filter.setCorners(topLeft.getLat(), topLeft.getLon(), bottomRight.getLat(), bottomRight.getLon());
|
||||
} else {
|
||||
+9
-17
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import static org.elasticsearch.index.query.Operator.*;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
@@ -44,8 +44,9 @@ import org.springframework.util.Assert;
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author James Bodkin
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class CriteriaQueryProcessor {
|
||||
|
||||
@Nullable
|
||||
@@ -174,17 +175,10 @@ class CriteriaQueryProcessor {
|
||||
|
||||
// operations without a value
|
||||
switch (key) {
|
||||
case EXISTS:
|
||||
query = existsQuery(fieldName);
|
||||
break;
|
||||
case EMPTY:
|
||||
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
|
||||
break;
|
||||
case NOT_EMPTY:
|
||||
query = wildcardQuery(fieldName, "*");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case EXISTS -> query = existsQuery(fieldName);
|
||||
case EMPTY -> query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
|
||||
case NOT_EMPTY -> query = wildcardQuery(fieldName, "*");
|
||||
default -> {}
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
@@ -237,8 +231,7 @@ class CriteriaQueryProcessor {
|
||||
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.AND);
|
||||
break;
|
||||
case IN:
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
if (isKeywordField) {
|
||||
query = boolQuery().must(termsQuery(fieldName, toStringList(iterable)));
|
||||
} else {
|
||||
@@ -247,8 +240,7 @@ class CriteriaQueryProcessor {
|
||||
}
|
||||
break;
|
||||
case NOT_IN:
|
||||
if (value instanceof Iterable) {
|
||||
Iterable<?> iterable = (Iterable<?>) value;
|
||||
if (value instanceof Iterable<?> iterable) {
|
||||
if (isKeywordField) {
|
||||
query = boolQuery().mustNot(termsQuery(fieldName, toStringList(iterable)));
|
||||
} else {
|
||||
+5
-3
@@ -13,20 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.cluster;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
|
||||
import org.springframework.data.elasticsearch.core.ResponseConverter;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ClusterOperations} using the {@link ElasticsearchRestTemplate}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class DefaultClusterOperations implements ClusterOperations {
|
||||
|
||||
private final ElasticsearchRestTemplate template;
|
||||
+4
-2
@@ -13,20 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.cluster;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ResponseConverter;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ReactiveClusterOperations} using the {@link ReactiveElasticsearchOperations}.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultReactiveClusterOperations implements ReactiveClusterOperations {
|
||||
private final ReactiveElasticsearchOperations operations;
|
||||
|
||||
+32
-109
@@ -13,35 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
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;
|
||||
@@ -83,10 +69,12 @@ import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.GetAliasesResponse;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.indices.*;
|
||||
import org.elasticsearch.client.tasks.TaskSubmissionResponse;
|
||||
import org.elasticsearch.core.TimeValue;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.index.reindex.ReindexRequest;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
@@ -103,22 +91,19 @@ import org.elasticsearch.xcontent.XContentType;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.elasticsearch.RestStatusException;
|
||||
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
|
||||
import org.springframework.data.elasticsearch.core.ResponseConverter;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ClientLogger;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Cluster;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
|
||||
import org.springframework.data.elasticsearch.client.util.NamedXContents;
|
||||
import org.springframework.data.elasticsearch.client.erhlc.HostProvider.Verification;
|
||||
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Cluster;
|
||||
import org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Indices;
|
||||
import org.springframework.data.elasticsearch.client.util.ScrollState;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
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;
|
||||
@@ -143,16 +128,18 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
|
||||
* @author Brian Clozel
|
||||
* @author Farid Faoudi
|
||||
* @author George Popides
|
||||
* @author Sijia Liu
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
// todo package private after refactoring
|
||||
@Deprecated
|
||||
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices, Cluster {
|
||||
|
||||
private final HostProvider<?> hostProvider;
|
||||
private final RequestCreator requestCreator;
|
||||
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
|
||||
private Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier = org.springframework.data.elasticsearch.support.HttpHeaders::new;
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server
|
||||
@@ -194,8 +181,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
Assert.notNull(headers, "HttpHeaders must not be null");
|
||||
Assert.notEmpty(hosts, "Elasticsearch Cluster needs to consist of at least one host");
|
||||
|
||||
var httpHeaders = new org.springframework.data.elasticsearch.support.HttpHeaders();
|
||||
httpHeaders.addAll(headers);
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(hosts)
|
||||
.withDefaultHeaders(headers).build();
|
||||
.withDefaultHeaders(httpHeaders).build();
|
||||
return create(clientConfiguration);
|
||||
}
|
||||
|
||||
@@ -227,7 +216,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]));
|
||||
@@ -239,84 +228,7 @@ 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) {
|
||||
public void setHeadersSupplier(Supplier<org.springframework.data.elasticsearch.support.HttpHeaders> headersSupplier) {
|
||||
|
||||
Assert.notNull(headersSupplier, "headersSupplier must not be null");
|
||||
|
||||
@@ -509,6 +421,17 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<BulkByScrollResponse> reindex(HttpHeaders headers, ReindexRequest reindexRequest) {
|
||||
return sendRequest(reindexRequest, requestCreator.reindex(), BulkByScrollResponse.class, headers).next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> submitReindex(HttpHeaders headers, ReindexRequest reindexRequest) {
|
||||
return sendRequest(reindexRequest, requestCreator.submitReindex(), TaskSubmissionResponse.class, headers).next()
|
||||
.map(TaskSubmissionResponse::getTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<T> execute(ReactiveElasticsearchClientCallback<T> callback) {
|
||||
|
||||
@@ -795,25 +718,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
|
||||
if (RawActionResponse.class.equals(responseType)) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
ClientLogger.logRawResponse(logId, response.statusCode().value());
|
||||
return Mono.just(responseType.cast(RawActionResponse.create(response)));
|
||||
}
|
||||
|
||||
if (response.statusCode().is5xxServerError()) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
ClientLogger.logRawResponse(logId, response.statusCode().value());
|
||||
return handleServerError(request, response);
|
||||
}
|
||||
|
||||
if (response.statusCode().is4xxClientError()) {
|
||||
|
||||
ClientLogger.logRawResponse(logId, response.statusCode());
|
||||
ClientLogger.logRawResponse(logId, response.statusCode().value());
|
||||
return handleClientError(logId, response, responseType);
|
||||
}
|
||||
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
.map(it -> new String(it, StandardCharsets.UTF_8)) //
|
||||
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) //
|
||||
.doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode().value(), it)) //
|
||||
.flatMap(content -> doDecode(response, responseType, content));
|
||||
}
|
||||
|
||||
@@ -890,7 +813,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
return response.body(BodyExtractors.toMono(byte[].class)) //
|
||||
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
|
||||
.flatMap(content -> contentOrError(content, mediaType, status)) //
|
||||
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode(), content)) //
|
||||
.doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode().value(), content)) //
|
||||
.flatMap(content -> doDecode(response, responseType, content));
|
||||
}
|
||||
|
||||
@@ -972,7 +895,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Status#hosts()
|
||||
* @see org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient.Status#hosts()
|
||||
*/
|
||||
@Override
|
||||
public Collection<ElasticsearchHost> hosts() {
|
||||
+3
-1
@@ -13,10 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
/**
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 4.0
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class DefaultRequestCreator implements RequestCreator {}
|
||||
+3
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
@@ -37,7 +37,9 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
* @author Huw Ayling-Miller
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class DefaultWebClientProvider implements WebClientProvider {
|
||||
|
||||
private final Map<InetSocketAddress, WebClient> cachedClients;
|
||||
+12
-6
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.document;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -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;
|
||||
|
||||
@@ -40,7 +41,11 @@ import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||
import org.springframework.data.elasticsearch.core.ResponseConverter;
|
||||
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.SearchDocumentResponse;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -61,7 +66,9 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Matt Gilene
|
||||
* @since 4.0
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public final class DocumentAdapters {
|
||||
|
||||
private DocumentAdapters() {}
|
||||
@@ -177,8 +184,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());
|
||||
@@ -710,10 +717,9 @@ public final class DocumentAdapters {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof SearchDocumentAdapter)) {
|
||||
if (!(o instanceof SearchDocumentAdapter that)) {
|
||||
return false;
|
||||
}
|
||||
SearchDocumentAdapter that = (SearchDocumentAdapter) o;
|
||||
return Float.compare(that.score, score) == 0 && delegate.equals(that.delegate);
|
||||
}
|
||||
|
||||
+4
-1
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregation;
|
||||
import org.springframework.data.elasticsearch.core.AggregationContainer;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,9 @@ import org.springframework.lang.NonNull;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.3
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
|
||||
|
||||
private final Aggregation aggregation;
|
||||
+4
-1
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.elasticsearch.search.aggregations.Aggregations;
|
||||
import org.springframework.data.elasticsearch.core.AggregationsContainer;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,9 @@ import org.springframework.lang.NonNull;
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.3
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchAggregations implements AggregationsContainer<Aggregations> {
|
||||
|
||||
private final Aggregations aggregations;
|
||||
+4
-2
@@ -13,15 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.cluster;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchClusterOperations {
|
||||
/**
|
||||
* Creates a ClusterOperations for a {@link ElasticsearchRestTemplate}.
|
||||
+10
-17
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -44,7 +44,9 @@ import org.springframework.util.StringUtils;
|
||||
* @author Roman Puchkovskiy
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
@Override
|
||||
@@ -54,17 +56,14 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
return new OptimisticLockingFailureException("Cannot index a document due to seq_no+primary_term conflict", ex);
|
||||
}
|
||||
|
||||
if (ex instanceof ElasticsearchException) {
|
||||
|
||||
ElasticsearchException elasticsearchException = (ElasticsearchException) ex;
|
||||
if (ex instanceof ElasticsearchException elasticsearchException) {
|
||||
|
||||
if (!indexAvailable(elasticsearchException)) {
|
||||
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
|
||||
ex);
|
||||
}
|
||||
|
||||
if (elasticsearchException instanceof ElasticsearchStatusException) {
|
||||
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
|
||||
if (elasticsearchException instanceof ElasticsearchStatusException elasticsearchStatusException) {
|
||||
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
|
||||
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
|
||||
}
|
||||
@@ -72,11 +71,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (ex instanceof RestStatusException) {
|
||||
RestStatusException restStatusException = (RestStatusException) ex;
|
||||
if (ex instanceof RestStatusException restStatusException) {
|
||||
Throwable cause = restStatusException.getCause();
|
||||
if (cause instanceof ElasticsearchException) {
|
||||
ElasticsearchException elasticsearchException = (ElasticsearchException) cause;
|
||||
if (cause instanceof ElasticsearchException elasticsearchException) {
|
||||
|
||||
if (!indexAvailable(elasticsearchException)) {
|
||||
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
|
||||
@@ -102,16 +99,14 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
Integer status = null;
|
||||
String message = null;
|
||||
|
||||
if (exception instanceof ElasticsearchStatusException) {
|
||||
if (exception instanceof ElasticsearchStatusException statusException) {
|
||||
|
||||
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
|
||||
status = statusException.status().getStatus();
|
||||
message = statusException.getMessage();
|
||||
}
|
||||
|
||||
if (exception instanceof RestStatusException) {
|
||||
if (exception instanceof RestStatusException statusException) {
|
||||
|
||||
RestStatusException statusException = (RestStatusException) exception;
|
||||
status = statusException.getStatus();
|
||||
message = statusException.getMessage();
|
||||
}
|
||||
@@ -121,9 +116,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
|
||||
&& message.contains("version conflict, required seqNo");
|
||||
}
|
||||
|
||||
if (exception instanceof VersionConflictEngineException) {
|
||||
|
||||
VersionConflictEngineException versionConflictEngineException = (VersionConflictEngineException) exception;
|
||||
if (exception instanceof VersionConflictEngineException versionConflictEngineException) {
|
||||
|
||||
return versionConflictEngineException.getMessage() != null
|
||||
&& versionConflictEngineException.getMessage().contains("version conflict, required seqNo");
|
||||
+64
-22
@@ -12,8 +12,8 @@
|
||||
* 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;
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -57,20 +57,25 @@ import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.elasticsearch.search.suggest.SuggestBuilder;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
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.RefreshPolicy;
|
||||
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.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.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -106,21 +111,27 @@ import org.springframework.util.Assert;
|
||||
* @author Gyula Attila Csorogi
|
||||
* @author Massimiliano Poggi
|
||||
* @author Farid Faoudi
|
||||
* @author Sijia Liu
|
||||
* @since 4.4
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ElasticsearchRestTemplate.class);
|
||||
|
||||
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) {
|
||||
@@ -130,12 +141,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
|
||||
@@ -262,7 +284,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
final UpdateByQueryRequest updateByQueryRequest = requestFactory.updateByQueryRequest(query, index);
|
||||
UpdateByQueryRequest updateByQueryRequest = requestFactory.updateByQueryRequest(query, index);
|
||||
|
||||
if (query.getRefreshPolicy() == null && getRefreshPolicy() != null) {
|
||||
updateByQueryRequest.setRefresh(getRefreshPolicy() == RefreshPolicy.IMMEDIATE);
|
||||
@@ -277,6 +299,26 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReindexResponse reindex(ReindexRequest reindexRequest) {
|
||||
|
||||
Assert.notNull(reindexRequest, "reindexRequest must not be null");
|
||||
|
||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequestES = requestFactory.reindexRequest(reindexRequest);
|
||||
BulkByScrollResponse bulkByScrollResponse = execute(
|
||||
client -> client.reindex(reindexRequestES, RequestOptions.DEFAULT));
|
||||
return ResponseConverter.reindexResponseOf(bulkByScrollResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String submitReindex(ReindexRequest reindexRequest) {
|
||||
|
||||
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,
|
||||
IndexCoordinates index) {
|
||||
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
|
||||
@@ -364,7 +406,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, documentCallback::doWith));
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||
}
|
||||
|
||||
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
|
||||
@@ -388,12 +430,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, documentCallback::doWith));
|
||||
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));
|
||||
@@ -403,7 +445,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, documentCallback::doWith));
|
||||
return callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,7 +459,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
SearchRequest searchRequest = requestFactory.searchRequest(suggestion, index);
|
||||
return execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
|
||||
@@ -436,7 +477,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(), documentCallback::doWith)));
|
||||
res.add(callback
|
||||
.doWith(SearchDocumentResponseBuilder.from(items[i].getResponse(), getEntityCreator(documentCallback))));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -469,7 +511,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
index);
|
||||
|
||||
SearchResponse response = items[i].getResponse();
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
|
||||
res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -502,7 +544,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
index);
|
||||
|
||||
SearchResponse response = items[i].getResponse();
|
||||
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
|
||||
res.add(callback.doWith(SearchDocumentResponseBuilder.from(response, getEntityCreator(documentCallback))));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -513,6 +555,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
|
||||
return items;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region ClientCallback
|
||||
@@ -568,7 +611,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
// region helper methods
|
||||
@Override
|
||||
protected String getClusterVersion() {
|
||||
public String getClusterVersion() {
|
||||
try {
|
||||
return execute(client -> client.info(RequestOptions.DEFAULT)).getVersion().getNumber();
|
||||
} catch (Exception ignored) {}
|
||||
@@ -590,16 +633,15 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getVendor() {
|
||||
public String getVendor() {
|
||||
return "Elasticsearch";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRuntimeLibraryVersion() {
|
||||
public String getRuntimeLibraryVersion() {
|
||||
return Version.CURRENT.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
|
||||
return suggest(suggestion, getIndexCoordinatesFor(clazz));
|
||||
+8
-8
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -32,10 +32,12 @@ 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
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class HighlightQueryBuilder {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
@@ -131,9 +133,8 @@ public class HighlightQueryBuilder {
|
||||
builder.highlighterType(parameters.getType());
|
||||
}
|
||||
|
||||
if (builder instanceof HighlightBuilder && parameters instanceof HighlightParameters) {
|
||||
HighlightBuilder highlightBuilder = (HighlightBuilder) builder;
|
||||
HighlightParameters highlightParameters = (HighlightParameters) parameters;
|
||||
if (builder instanceof HighlightBuilder highlightBuilder
|
||||
&& parameters instanceof HighlightParameters highlightParameters) {
|
||||
|
||||
if (StringUtils.hasLength(highlightParameters.getEncoder())) {
|
||||
highlightBuilder.encoder(highlightParameters.getEncoder());
|
||||
@@ -144,9 +145,8 @@ public class HighlightQueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if (builder instanceof HighlightBuilder.Field && parameters instanceof HighlightFieldParameters) {
|
||||
HighlightBuilder.Field field = (HighlightBuilder.Field) builder;
|
||||
HighlightFieldParameters fieldParameters = (HighlightFieldParameters) parameters;
|
||||
if (builder instanceof HighlightBuilder.Field field
|
||||
&& parameters instanceof HighlightFieldParameters fieldParameters) {
|
||||
|
||||
if ((fieldParameters).getFragmentOffset() > -1) {
|
||||
field.fragmentOffset(fieldParameters.getFragmentOffset());
|
||||
+28
-3
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -36,7 +36,9 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface HostProvider<T extends HostProvider<T>> {
|
||||
|
||||
/**
|
||||
@@ -99,11 +101,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);
|
||||
|
||||
+6
-4
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -45,7 +45,9 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
|
||||
|
||||
private final static Log LOGGER = LogFactory.getLog(MultiNodeHostProvider.class);
|
||||
@@ -68,7 +70,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#clusterInfo()
|
||||
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#clusterInfo()
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClusterInformation> clusterInfo() {
|
||||
@@ -78,7 +80,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#createWebClient(java.net.InetSocketAddress)
|
||||
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#createWebClient(java.net.InetSocketAddress)
|
||||
*/
|
||||
@Override
|
||||
public WebClient createWebClient(InetSocketAddress endpoint) {
|
||||
@@ -87,7 +89,7 @@ class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.client.reactive.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification)
|
||||
* @see org.springframework.data.elasticsearch.client.erhlc.HostProvider#lookupActiveHost(org.springframework.data.elasticsearch.client.erhlc.HostProvider.Verification)
|
||||
*/
|
||||
@Override
|
||||
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
|
||||
+3
-2
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.util;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -78,7 +78,6 @@ import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
|
||||
import org.elasticsearch.xcontent.ContextParser;
|
||||
import org.elasticsearch.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.xcontent.ParseField;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -92,7 +91,9 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
|
||||
*
|
||||
* @author Russell Parry
|
||||
* @since 4.0
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class NamedXContents {
|
||||
|
||||
private NamedXContents() {
|
||||
+35
-8
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -21,12 +21,15 @@ import java.util.List;
|
||||
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder;
|
||||
import org.elasticsearch.search.SearchExtBuilder;
|
||||
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
||||
import org.elasticsearch.search.aggregations.PipelineAggregationBuilder;
|
||||
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.elasticsearch.core.query.BaseQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexBoost;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -41,7 +44,10 @@ import org.springframework.lang.Nullable;
|
||||
* @author Jean-Baptiste Nizet
|
||||
* @author Martin Choraine
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sijia Liu
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class NativeSearchQuery extends BaseQuery {
|
||||
|
||||
@Nullable private final QueryBuilder query;
|
||||
@@ -53,12 +59,11 @@ public class NativeSearchQuery extends BaseQuery {
|
||||
@Nullable private List<PipelineAggregationBuilder> pipelineAggregations;
|
||||
@Nullable private HighlightBuilder highlightBuilder;
|
||||
@Nullable private HighlightBuilder.Field[] highlightFields;
|
||||
@Nullable private List<IndexBoost> indicesBoost;
|
||||
@Nullable private SearchTemplateRequestBuilder searchTemplate;
|
||||
@Nullable private SuggestBuilder suggestBuilder;
|
||||
@Nullable private List<SearchExtBuilder> searchExtBuilders;
|
||||
|
||||
public NativeSearchQuery(@Nullable QueryBuilder query) {
|
||||
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@@ -96,6 +101,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;
|
||||
@@ -169,11 +185,6 @@ public class NativeSearchQuery extends BaseQuery {
|
||||
this.pipelineAggregations = pipelineAggregationBuilders;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<IndexBoost> getIndicesBoost() {
|
||||
return indicesBoost;
|
||||
}
|
||||
|
||||
public void setIndicesBoost(List<IndexBoost> indicesBoost) {
|
||||
this.indicesBoost = indicesBoost;
|
||||
}
|
||||
@@ -201,4 +212,20 @@ public class NativeSearchQuery extends BaseQuery {
|
||||
public SuggestBuilder getSuggestBuilder() {
|
||||
return suggestBuilder;
|
||||
}
|
||||
|
||||
public void setSearchExtBuilders(List<SearchExtBuilder> searchExtBuilders) {
|
||||
this.searchExtBuilders = searchExtBuilders;
|
||||
}
|
||||
|
||||
public void addSearchExtBuilder(SearchExtBuilder searchExtBuilder) {
|
||||
if (searchExtBuilders == null) {
|
||||
searchExtBuilders = new ArrayList<>();
|
||||
}
|
||||
searchExtBuilders.add(searchExtBuilder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<SearchExtBuilder> getSearchExtBuilders() {
|
||||
return searchExtBuilders;
|
||||
}
|
||||
}
|
||||
+25
-128
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.query;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import static org.springframework.util.CollectionUtils.*;
|
||||
|
||||
@@ -32,7 +32,10 @@ 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.data.elasticsearch.core.query.BaseQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -50,8 +53,11 @@ import org.springframework.lang.Nullable;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Peer Mueller
|
||||
* @author vdisk
|
||||
* @author owen.qq
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
public class NativeSearchQueryBuilder {
|
||||
@Deprecated
|
||||
public class NativeSearchQueryBuilder extends BaseQueryBuilder<NativeSearchQuery, NativeSearchQueryBuilder> {
|
||||
|
||||
@Nullable private QueryBuilder queryBuilder;
|
||||
@Nullable private QueryBuilder filterBuilder;
|
||||
@@ -61,25 +67,15 @@ 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<>();
|
||||
@Nullable private SuggestBuilder suggestBuilder;
|
||||
@Nullable private List<Object> searchAfter;
|
||||
|
||||
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
|
||||
this.queryBuilder = queryBuilder;
|
||||
@@ -203,42 +199,11 @@ public class NativeSearchQueryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withIndicesBoost(Collection<IndexBoost> indicesBoost) {
|
||||
this.indicesBoost.addAll(indicesBoost);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
public NativeSearchQueryBuilder withIndicesBoost(IndexBoost... indicesBoost) {
|
||||
Collections.addAll(this.indicesBoost, indicesBoost);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withSearchTemplate(SearchTemplateRequestBuilder searchTemplateBuilder) {
|
||||
this.searchTemplateBuilder = searchTemplateBuilder;
|
||||
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) {
|
||||
@@ -258,44 +223,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;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withSearchType(SearchType searchType) {
|
||||
this.searchType = searchType;
|
||||
return this;
|
||||
@@ -306,16 +233,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
|
||||
*/
|
||||
@@ -342,34 +259,29 @@ public class NativeSearchQueryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public NativeSearchQueryBuilder withSearchAfter(List<Object> searchAfter) {
|
||||
if (searchAfter != null && searchAfter.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.searchAfter = searchAfter;
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (searchTemplateBuilder != null) {
|
||||
nativeSearchQuery.setSearchTemplate(searchTemplateBuilder);
|
||||
}
|
||||
@@ -390,18 +302,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);
|
||||
}
|
||||
|
||||
if (searchType != null) {
|
||||
nativeSearchQuery.setSearchType(Query.SearchType.valueOf(searchType.name()));
|
||||
}
|
||||
@@ -410,14 +310,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) {
|
||||
@@ -431,6 +323,11 @@ public class NativeSearchQueryBuilder {
|
||||
if (suggestBuilder != null) {
|
||||
nativeSearchQuery.setSuggestBuilder(suggestBuilder);
|
||||
}
|
||||
|
||||
if (searchAfter != null) {
|
||||
nativeSearchQuery.setSearchAfter(searchAfter);
|
||||
}
|
||||
|
||||
return nativeSearchQuery;
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
@@ -32,8 +32,11 @@ import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class RawActionResponse extends ActionResponse {
|
||||
|
||||
private final ClientResponse delegate;
|
||||
@@ -46,7 +49,7 @@ class RawActionResponse extends ActionResponse {
|
||||
return new RawActionResponse(response);
|
||||
}
|
||||
|
||||
public HttpStatus statusCode() {
|
||||
public HttpStatusCode statusCode() {
|
||||
return delegate.statusCode();
|
||||
}
|
||||
|
||||
+5
-1
@@ -13,16 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.cluster;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
|
||||
|
||||
/**
|
||||
* Reactive Elasticsearch operations on cluster level.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ReactiveClusterOperations {
|
||||
|
||||
/**
|
||||
+75
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -53,6 +53,7 @@ import org.elasticsearch.client.indices.*;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.index.reindex.ReindexRequest;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||
import org.elasticsearch.script.mustache.SearchTemplateRequest;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
@@ -76,10 +77,13 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Henrique Amaral
|
||||
* @author Thomas Geese
|
||||
* @author Farid Faoudi
|
||||
* @author Sijia Liu
|
||||
* @since 3.2
|
||||
* @see ClientConfiguration
|
||||
* @see ReactiveRestClients
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ReactiveElasticsearchClient {
|
||||
|
||||
/**
|
||||
@@ -713,6 +717,76 @@ public interface ReactiveElasticsearchClient {
|
||||
*/
|
||||
Mono<BulkResponse> bulk(HttpHeaders headers, BulkRequest bulkRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param consumer must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the response
|
||||
* @since 4.4
|
||||
*/
|
||||
default Mono<BulkByScrollResponse> reindex(Consumer<ReindexRequest> consumer) {
|
||||
|
||||
ReindexRequest reindexRequest = new ReindexRequest();
|
||||
consumer.accept(reindexRequest);
|
||||
return reindex(reindexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param reindexRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the response
|
||||
* @since 4.4
|
||||
*/
|
||||
default Mono<BulkByScrollResponse> reindex(ReindexRequest reindexRequest) {
|
||||
return reindex(HttpHeaders.EMPTY, reindexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param reindexRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the response
|
||||
* @since 4.4
|
||||
*/
|
||||
Mono<BulkByScrollResponse> reindex(HttpHeaders headers, ReindexRequest reindexRequest);
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param consumer must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the task id
|
||||
* @since 4.4
|
||||
*/
|
||||
default Mono<String> submitReindex(Consumer<ReindexRequest> consumer) {
|
||||
|
||||
ReindexRequest reindexRequest = new ReindexRequest();
|
||||
consumer.accept(reindexRequest);
|
||||
return submitReindex(reindexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param reindexRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the task id
|
||||
* @since 4.4
|
||||
*/
|
||||
default Mono<String> submitReindex(ReindexRequest reindexRequest) {
|
||||
return submitReindex(HttpHeaders.EMPTY, reindexRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link ReindexRequest} against the {@literal reindex} API.
|
||||
*
|
||||
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
|
||||
* @param reindexRequest must not be {@literal null}
|
||||
* @return the {@link Mono} emitting the task id
|
||||
* @since 4.4
|
||||
*/
|
||||
Mono<String> submitReindex(HttpHeaders headers, ReindexRequest reindexRequest);
|
||||
|
||||
/**
|
||||
* Compose the actual command/s to run against Elasticsearch using the underlying {@link WebClient connection}.
|
||||
* {@link #execute(ReactiveElasticsearchClientCallback) Execute} selects an active server from the available ones and
|
||||
+88
-589
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -23,11 +23,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
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;
|
||||
@@ -47,49 +43,31 @@ import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.reindex.BulkByScrollResponse;
|
||||
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
|
||||
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
|
||||
import org.elasticsearch.search.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.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.ReactiveIndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.RefreshPolicy;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitMapping;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||
import org.springframework.data.elasticsearch.core.SearchPage;
|
||||
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.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.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.data.elasticsearch.core.reindex.ReindexRequest;
|
||||
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -105,100 +83,42 @@ import org.springframework.util.Assert;
|
||||
* @author Russell Parry
|
||||
* @author Thomas Geese
|
||||
* @author Farid Faoudi
|
||||
* @author Sijia Liu
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOperations, ApplicationContextAware {
|
||||
|
||||
private static final Log QUERY_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.core.QUERY");
|
||||
@Deprecated
|
||||
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}.
|
||||
*
|
||||
@@ -208,58 +128,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) //
|
||||
@@ -273,7 +149,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();
|
||||
|
||||
@@ -286,51 +163,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) {
|
||||
|
||||
@@ -350,16 +182,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.
|
||||
@@ -371,7 +193,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)) //
|
||||
@@ -399,17 +232,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)));
|
||||
}
|
||||
|
||||
@@ -425,54 +248,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
|
||||
@@ -480,13 +266,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)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,55 +281,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
*/
|
||||
protected Mono<GetResult> doGet(GetRequest request) {
|
||||
|
||||
return Mono.from(execute(client -> client.get(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.empty());
|
||||
return Mono.from(execute(client -> client.get(request)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (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);
|
||||
@@ -610,11 +349,28 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
|
||||
return delete(query, entityType, getIndexCoordinatesFor(entityType));
|
||||
public Mono<ReindexResponse> reindex(ReindexRequest postReindexRequest) {
|
||||
|
||||
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
|
||||
|
||||
return Mono.defer(() -> {
|
||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
|
||||
return Mono.from(execute(client -> client.reindex(reindexRequest))).map(ResponseConverter::reindexResponseOf);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
@Override
|
||||
public Mono<String> submitReindex(ReindexRequest postReindexRequest) {
|
||||
|
||||
Assert.notNull(postReindexRequest, "postReindexRequest must not be null");
|
||||
|
||||
return Mono.defer(() -> {
|
||||
org.elasticsearch.index.reindex.ReindexRequest reindexRequest = requestFactory.reindexRequest(postReindexRequest);
|
||||
return Mono.from(execute(client -> client.submitReindex(reindexRequest)));
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<BulkByScrollResponse> doDeleteBy(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
DeleteByQueryRequest request = requestFactory.deleteByQueryRequest(query, entityType, index);
|
||||
@@ -723,16 +479,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) {
|
||||
@@ -754,32 +500,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(() -> {
|
||||
|
||||
@@ -795,24 +516,21 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
});
|
||||
}
|
||||
|
||||
private 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);
|
||||
request = prepareSearchRequest(request, false);
|
||||
|
||||
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
|
||||
Function<SearchDocument, Object> entityCreator = searchDocument -> documentCallback.toEntity(searchDocument)
|
||||
.block();
|
||||
// noinspection unchecked
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> ((Mono<T>) documentCallback
|
||||
.toEntity(searchDocument)).toFuture();
|
||||
|
||||
return doFindForResponse(request, entityCreator);
|
||||
});
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
@@ -843,56 +561,7 @@ 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) {
|
||||
return doSuggest(suggestion, getIndexCoordinatesFor(entityType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return doSuggest(suggestion, index);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private Flux<org.elasticsearch.search.suggest.Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
|
||||
return Flux.defer(() -> {
|
||||
SearchRequest request = requestFactory.searchRequest(suggestion, index);
|
||||
return Flux.from(execute(client -> client.suggest(request)));
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -924,15 +593,15 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
* @param entityCreator
|
||||
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
|
||||
*/
|
||||
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
|
||||
Function<SearchDocument, ? extends Object> entityCreator) {
|
||||
protected <T> Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
|
||||
SearchDocumentResponse.EntityCreator<T> entityCreator) {
|
||||
|
||||
if (QUERY_LOGGER.isDebugEnabled()) {
|
||||
QUERY_LOGGER.debug(String.format("Executing doFindForResponse: %s", request));
|
||||
}
|
||||
|
||||
return Mono.from(execute(client -> client.searchForResponse(request)))
|
||||
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
|
||||
.map(searchResponse -> SearchDocumentResponseBuilder.from(searchResponse, entityCreator));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -947,8 +616,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
QUERY_LOGGER.debug(String.format("Executing doCount: %s", request));
|
||||
}
|
||||
|
||||
return Mono.from(execute(client -> client.count(request))) //
|
||||
.onErrorResume(NoSuchIndexException.class, it -> Mono.just(0L));
|
||||
return Mono.from(execute(client -> client.count(request)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -988,11 +656,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
return request;
|
||||
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Helper methods
|
||||
protected Mono<String> getClusterVersion() {
|
||||
@Override
|
||||
public Mono<String> getClusterVersion() {
|
||||
try {
|
||||
return Mono.from(execute(ReactiveElasticsearchClient::info))
|
||||
.map(mainResponse -> mainResponse.getVersion().toString());
|
||||
@@ -1004,7 +672,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
* @return the vendor name of the used cluster and client library
|
||||
* @since 4.3
|
||||
*/
|
||||
protected Mono<String> getVendor() {
|
||||
@Override
|
||||
public Mono<String> getVendor() {
|
||||
return Mono.just("Elasticsearch");
|
||||
}
|
||||
|
||||
@@ -1012,7 +681,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
|
||||
* @return the version of the used client runtime library.
|
||||
* @since 4.3
|
||||
*/
|
||||
protected Mono<String> getRuntimeLibraryVersion() {
|
||||
@Override
|
||||
public Mono<String> getRuntimeLibraryVersion() {
|
||||
return Mono.just(Version.CURRENT.toString());
|
||||
}
|
||||
|
||||
@@ -1046,11 +716,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);
|
||||
@@ -1066,17 +731,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.
|
||||
*
|
||||
@@ -1131,159 +785,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-20
@@ -13,11 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import static org.elasticsearch.client.Requests.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.IndexInformation;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveResourceUtil;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -62,7 +66,9 @@ import org.springframework.util.Assert;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author George Popides
|
||||
* @since 4.1
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
class ReactiveIndexTemplate implements ReactiveIndexOperations {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ReactiveIndexTemplate.class);
|
||||
@@ -180,13 +186,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 +237,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 +358,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");
|
||||
+5
-1
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.reactive;
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -29,7 +29,9 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public final class ReactiveRestClients {
|
||||
|
||||
private ReactiveRestClients() {}
|
||||
@@ -70,7 +72,9 @@ public final class ReactiveRestClients {
|
||||
* the ReactiveElasticsearchClient with a {@link WebClient}
|
||||
*
|
||||
* @since 4.3
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
|
||||
|
||||
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user