Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 230616a3c3 | |||
| 44ae2b2af5 | |||
| e934a2eea8 | |||
| 468b573b77 | |||
| 9a442df5b9 | |||
| 7b96041bc3 | |||
| ac73ba4774 | |||
| fa7a285606 | |||
| 939179ced4 | |||
| a45281b1fd | |||
| 474ff84d6e | |||
| c58e3b3a8f | |||
| 4ed7f96919 | |||
| 776a30af75 | |||
| c92480b97d | |||
| 6e8357caa7 | |||
| 541faff07f | |||
| 9aeac006d1 | |||
| 6e52b97b76 | |||
| c7550e8d82 | |||
| c17159ce1c | |||
| 0a1d10f8b4 | |||
| ef0e47c6bb | |||
| 7e2ebb299c | |||
| 594566a44a | |||
| bf3248b536 | |||
| c59bb0b434 | |||
| d93d1e0738 | |||
| aebdc8f86b | |||
| 2be27593d6 | |||
| aa22d8239d | |||
| 6746bc5278 | |||
| a6cb959605 | |||
| e04905a1a7 | |||
| 834b10f578 | |||
| 823cfa919a | |||
| ab29ae4219 | |||
| 62fb89208a | |||
| e8c3badc56 | |||
| 187c9bbd5d | |||
| e1a5811406 | |||
| b643669d36 | |||
| f66af53480 | |||
| 14099970bb | |||
| 0ab253422f | |||
| 0d2a6b98e8 | |||
| 30602496bd | |||
| 95762b4fde | |||
| 0771e90031 | |||
| 6729330500 | |||
| 8796292611 | |||
| f3f9ca4002 | |||
| 083a38ed57 | |||
| 4f3aa52958 | |||
| 3256a2bfe0 | |||
| 95401a5bd7 | |||
| 12cd64cfc8 | |||
| 7e557317d1 | |||
| c9846ab8ad |
@@ -20,7 +20,3 @@ target
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
/.env
|
||||
|
||||
|
||||
/zap.env
|
||||
|
||||
+86
-84
@@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
@@ -21,95 +20,98 @@ 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 transport directory: " + baseDirectory.getAbsolutePath());
|
||||
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());
|
||||
|
||||
// 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 @@
|
||||
#Tue Jun 13 08:52:21 CEST 2023
|
||||
#Fri Jun 03 09:42:29 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.9.2/apache-maven-3.9.2-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,7 +5,3 @@ 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
|
||||
|
||||
Test 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
+55
-20
@@ -1,7 +1,7 @@
|
||||
def p = [:]
|
||||
node {
|
||||
checkout scm
|
||||
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
|
||||
checkout scm
|
||||
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
|
||||
}
|
||||
|
||||
pipeline {
|
||||
@@ -9,7 +9,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/3.0.x", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@@ -32,14 +32,18 @@ pipeline {
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials("${p['docker.credentials']}")
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,9 +51,8 @@ pipeline {
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
allOf {
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
branch 'main'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
@@ -59,14 +62,44 @@ pipeline {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials("${p['docker.credentials']}")
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.next.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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (LTS)") {
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials("${p['docker.credentials']}")
|
||||
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.lts.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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,15 +126,17 @@ pipeline {
|
||||
|
||||
steps {
|
||||
script {
|
||||
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} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=libs-snapshot-local " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
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} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=libs-snapshot-local " +
|
||||
"-Dartifactory.build-name=spring-data-elasticsearch " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-44
@@ -4,48 +4,22 @@ image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elas
|
||||
|
||||
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
|
||||
|
||||
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine.
|
||||
Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
|
||||
The Spring Data Elasticsearch project provides integration with the https://www.elastic.co/[Elasticsearch] search engine. Key functional areas of Spring Data Elasticsearch are a POJO centric model for interacting with a Elasticsearch Documents and easily writing a Repository style data access layer.
|
||||
|
||||
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.
|
||||
* `ElasticsearchOperations` class and implementations that increases productivity performing common ES operations.
|
||||
Includes integrated object mapping between documents and POJOs.
|
||||
* `ElasticsearchRestTemplate` helper class 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].
|
||||
By participating, you are expected to uphold this code of conduct.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Getting Started
|
||||
|
||||
@@ -84,6 +58,31 @@ public class MyService {
|
||||
}
|
||||
----
|
||||
|
||||
=== Using Transport Client
|
||||
|
||||
NOTE: Usage of the TransportClient is deprecated as of version 4.0, use RestClient instead.
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public Client elasticsearchClient() throws UnknownHostException {
|
||||
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
|
||||
TransportClient client = new PreBuiltTransportClient(settings);
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
|
||||
return new ElasticsearchTemplate(elasticsearchClient());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Using the RestClient
|
||||
|
||||
Provide a configuration like this:
|
||||
@@ -137,9 +136,9 @@ To use the Release candidate versions of the upcoming major version, use our Mav
|
||||
</dependency>
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
|
||||
@@ -154,44 +153,40 @@ If you'd rather like the latest snapshots of the upcoming major version, use our
|
||||
</dependency>
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
----
|
||||
|
||||
== Getting Help
|
||||
|
||||
Having trouble with Spring Data?
|
||||
We’d love to help!
|
||||
Having trouble with Spring Data? We’d love to help!
|
||||
|
||||
* Check the
|
||||
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/elasticsearch/docs/current/api/[Javadocs].
|
||||
* Learn the Spring basics – Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
|
||||
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
|
||||
* If you are upgrading, check out the https://docs.spring.io/spring-data/elasticsearch/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
|
||||
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-elasticsearch`].
|
||||
You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
|
||||
* Report bugs with Spring Data for Elasticsearch at https://github.com/spring-projects/spring-data-elasticsearch/issues[https://github.com/spring-projects/spring-data-elasticsearch/issues].
|
||||
|
||||
== Reporting Issues
|
||||
|
||||
Spring Data uses GitHub as issue tracking system to record bugs and feature requests.
|
||||
If you want to raise an issue, please follow the recommendations below:
|
||||
Spring Data uses GitHub as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
|
||||
|
||||
* Before you log a bug, please search the
|
||||
https://github.com/spring-projects/spring-data-elasticsearch/issues[issue tracker] to see if someone has already reported the problem.
|
||||
* If the issue doesn’t already exist, https://github.com/spring-projects/spring-data-elasticsearch/issues/new[create a new issue].
|
||||
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data Elasticsearch that you are using and JVM version.
|
||||
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes before and after your text.
|
||||
* If possible try to create a test-case or project that replicates the issue.
|
||||
Attach a link to your code or a compressed file containing your code.
|
||||
* If possible try to create a test-case or project that replicates the issue. 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 need JDK 17 or above to build the _main_ branch.
|
||||
For the branches up to and including release 4.4, JDK 8 is required.
|
||||
You also need JDK 1.8.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
@@ -202,8 +197,12 @@ If you want to build with the regular `mvn` command, you will need https://maven
|
||||
|
||||
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before submitting your first pull request._
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.6_10-jdk-focal
|
||||
java.next.tag=20-jdk-jammy
|
||||
java.main.tag=8u332-b09-jdk
|
||||
java.next.tag=11.0.15_10-jdk
|
||||
java.lts.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}
|
||||
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
|
||||
docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.18
|
||||
docker.mongodb.5.0.version=5.0.14
|
||||
docker.mongodb.6.0.version=6.0.4
|
||||
docker.mongodb.4.0.version=4.0.28
|
||||
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.10
|
||||
docker.redis.6.version=6.2.6
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.14
|
||||
docker.cassandra.3.version=3.11.12
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
|
||||
@@ -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 transport directory
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>5.0.7</version>
|
||||
<version>4.3.7</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>3.0.7</version>
|
||||
<version>2.6.7</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,22 +18,12 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<springdata.commons>3.0.7</springdata.commons>
|
||||
|
||||
<!-- version of the RestHighLevelClient -->
|
||||
<elasticsearch-rhlc>7.17.9</elasticsearch-rhlc>
|
||||
<!-- version of the new ElasticsearchClient -->
|
||||
<elasticsearch-java>8.5.3</elasticsearch-java>
|
||||
|
||||
<log4j>2.18.0</log4j>
|
||||
<elasticsearch>7.15.2</elasticsearch>
|
||||
<log4j>2.17.0</log4j>
|
||||
<netty>4.1.65.Final</netty>
|
||||
|
||||
<blockhound-junit>1.0.7.RELEASE</blockhound-junit>
|
||||
<hoverfly>0.14.3</hoverfly>
|
||||
<jsonassert>1.5.1</jsonassert>
|
||||
<testcontainers>1.17.3</testcontainers>
|
||||
<wiremock>2.33.2</wiremock>
|
||||
|
||||
<springdata.commons>2.6.7</springdata.commons>
|
||||
<testcontainers>1.15.3</testcontainers>
|
||||
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
|
||||
<!--
|
||||
@@ -111,6 +101,12 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -144,12 +140,11 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- optional Elasticsearch RestHighLevelClient, deprecated in SDE 5.0 -->
|
||||
<!-- Elasticsearch -->
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>${elasticsearch-rhlc}</version>
|
||||
<optional>true</optional>
|
||||
<artifactId>transport</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
@@ -158,11 +153,11 @@
|
||||
</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>
|
||||
<!-- required by elasticsearch -->
|
||||
<groupId>org.elasticsearch.plugin</groupId>
|
||||
<artifactId>transport-netty4-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
@@ -170,10 +165,11 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch.client</groupId>
|
||||
<artifactId>elasticsearch-rest-client</artifactId> <!-- is Apache 2-->
|
||||
<version>${elasticsearch-java}</version>
|
||||
<artifactId>elasticsearch-rest-high-level-client</artifactId>
|
||||
<version>${elasticsearch}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
@@ -182,6 +178,21 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<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>
|
||||
|
||||
<!-- Jackson JSON Mapper -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -193,6 +204,13 @@
|
||||
</dependency>
|
||||
|
||||
<!-- CDI -->
|
||||
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
@@ -202,40 +220,23 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.enterprise</groupId>
|
||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<groupId>javax.enterprise</groupId>
|
||||
<artifactId>cdi-api</artifactId>
|
||||
<version>${cdi}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
<version>${jakarta-annotation-api}</version>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax-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>
|
||||
@@ -253,18 +254,6 @@
|
||||
</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>
|
||||
@@ -279,17 +268,34 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--
|
||||
we don't use lombok in Spring Data Elasticsearch anymore. But the dependency is set in the parent project, and so the
|
||||
lombok compiler stuff is executed regardless of the fact that we don't need it.
|
||||
On AdoptOpenJdk 16.0.0 this leads to an error, so the project does not build.
|
||||
Therefore we replace lombok with a jar - that just contains an empty file - that lives in a local maven repository in
|
||||
src/test/resources/local-maven-repo/
|
||||
It was installed with
|
||||
mvn deploy:deploy-file -DgroupId=org.projectlombok -DartifactId=lombok -Dversion=999999 -Durl=file:./src/test/resources/local-maven-repo/ -DrepositoryId=local-maven-repo -DupdateReleaseInfo=true -Dfile=path/to/empty.jar
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>999999</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.skyscreamer</groupId>
|
||||
<artifactId>jsonassert</artifactId>
|
||||
<version>${jsonassert}</version>
|
||||
<version>1.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8</artifactId>
|
||||
<version>${wiremock}</version>
|
||||
<version>2.26.3</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- these exclusions are needed because of Elasticsearch JarHell-->
|
||||
@@ -307,7 +313,7 @@
|
||||
<dependency>
|
||||
<groupId>io.specto</groupId>
|
||||
<artifactId>hoverfly-java-junit5</artifactId>
|
||||
<version>${hoverfly}</version>
|
||||
<version>0.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -420,7 +426,7 @@
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.7.5</version>
|
||||
<version>1.5.2</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.pitest</groupId>
|
||||
@@ -429,9 +435,6 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<jvmArgs>
|
||||
<jvmArg>-XX:+AllowRedefinitionToAddDeleteMethods</jvmArg>
|
||||
</jvmArgs>
|
||||
<excludedGroups>integration-test</excludedGroups>
|
||||
<targetClasses>
|
||||
<param>org.springframework.data.elasticsearch.core.geo.*</param>
|
||||
@@ -501,10 +504,22 @@
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
|
||||
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-release</id>
|
||||
<url>https://repo.spring.io/libs-release</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>local-maven-repo</id>
|
||||
<url>file:///${project.basedir}/src/test/resources/local-maven-repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-release</id>
|
||||
<url>https://repo.spring.io/plugins-release</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -5,15 +5,13 @@ BioMed Central Development Team; Oliver Drotbohm; Greg Turnquist; Christoph Stro
|
||||
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
|
||||
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
|
||||
|
||||
(C) 2013-2022 The original author(s).
|
||||
(C) 2013-2021 The original author(s).
|
||||
|
||||
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
toc::[]
|
||||
|
||||
include::preface.adoc[]
|
||||
include::{spring-data-commons-docs}/upgrade.adoc[leveloffset=+1]
|
||||
|
||||
|
||||
:leveloffset: +1
|
||||
include::{spring-data-commons-docs}/repositories.adoc[]
|
||||
|
||||
@@ -17,9 +17,9 @@ include::reference/elasticsearch-new.adoc[leveloffset=+1]
|
||||
* Version Control - https://github.com/spring-projects/spring-data-elasticsearch
|
||||
* API Documentation - https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
|
||||
* Bugtracker - https://github.com/spring-projects/spring-data-elasticsearch/issues
|
||||
* Release repository - https://repo1.maven.org/maven2/
|
||||
* Milestone repository - https://repo.spring.io/milestone/
|
||||
* Snapshot repository - https://repo.spring.io/snapshot/
|
||||
* Release repository - https://repo.spring.io/libs-release
|
||||
* Milestone repository - https://repo.spring.io/libs-milestone
|
||||
* Snapshot repository - https://repo.spring.io/libs-snapshot
|
||||
|
||||
[[preface.requirements]]
|
||||
== Requirements
|
||||
@@ -29,25 +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 Elasticsearch version given shows with which client libraries Spring Data Elasticsearch was
|
||||
built and tested.
|
||||
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:
|
||||
|
||||
[cols="^,^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
|
||||
| 2022.0 (Turing) | 5.0.x | 8.5.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.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
|
||||
| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.15.2 | 5.3.xfootnote:cdv[] | 2.5 .xfootnote:cdv[]
|
||||
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x
|
||||
| 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x
|
||||
| Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x
|
||||
| Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x
|
||||
| Lovelacefootnote:oom[Out of maintenance] | 3.1.xfootnote:oom[] | 6.2.2 | 5.1.19 |2.1.x
|
||||
| Kayfootnote:oom[] | 3.0.xfootnote:oom[] | 5.5.0 | 5.0.13 | 2.0.x
|
||||
| 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.operations,ElasticsearchOperations interface>>.
|
||||
Support for upcoming versions of Elasticsearch is being tracked and general compatibility should be given assuming the usage of the <<elasticsearch.clients.rest,high-level REST client>>.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[[elasticsearch.auditing]]
|
||||
== Elasticsearch Auditing
|
||||
|
||||
[[elasticsearch.auditing.preparing]]
|
||||
=== Preparing entities
|
||||
|
||||
In order for the auditing code to be able to decide whether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
|
||||
@@ -55,7 +54,6 @@ public class Person implements Persistable<Long> {
|
||||
<.> the getter is the required implementation from the interface
|
||||
<.> an object is new if it either has no `id` or none of fields containing creation attributes are set.
|
||||
|
||||
[[elasticsearch.auditing.activating]]
|
||||
=== Activating auditing
|
||||
|
||||
After the entities have been set up and providing the `AuditorAware` - or `ReactiveAuditorAware` - the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
|
||||
|
||||
@@ -3,126 +3,66 @@
|
||||
|
||||
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
|
||||
|
||||
Spring Data Elasticsearch operates upon an Elasticsearch client (provided by Elasticsearch client libraries) that is
|
||||
connected to a single Elasticsearch node or a cluster.
|
||||
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data
|
||||
Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
|
||||
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
|
||||
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
|
||||
|
||||
[[elasticsearch.clients.restclient]]
|
||||
== Imperative Rest Client
|
||||
[[elasticsearch.clients.transport]]
|
||||
== Transport Client
|
||||
|
||||
To use the imperative (non-reactive) client, a configuration bean must be configured like this:
|
||||
WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]).
|
||||
Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
|
||||
|
||||
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
|
||||
|
||||
.Transport Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class MyClientConfig extends ElasticsearchConfiguration {
|
||||
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() <.>
|
||||
.connectedTo("localhost:9200")
|
||||
.build();
|
||||
}
|
||||
@Bean
|
||||
public Client elasticsearchClient() throws UnknownHostException {
|
||||
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); <.>
|
||||
TransportClient client = new PreBuiltTransportClient(settings);
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); <.>
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {
|
||||
|
||||
ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
|
||||
template.setRefreshPolicy(refreshPolicy()); <.>
|
||||
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
IndexRequest request = new IndexRequest("spring-data")
|
||||
.id(randomID())
|
||||
.source(someObject);
|
||||
|
||||
IndexResponse response = client.index(request);
|
||||
----
|
||||
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
|
||||
|
||||
<.> The `TransportClient` must be configured with the cluster name.
|
||||
<.> The host and port to connect the client to.
|
||||
<.> the RefreshPolicy must be set in the `ElasticsearchTemplate` (override `refreshPolicy()` to not use the default)
|
||||
====
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
[[elasticsearch.clients.rest]]
|
||||
== High Level REST Client
|
||||
|
||||
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns the very same request/response objects and therefore depends on the Elasticsearch core project.
|
||||
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
|
||||
|
||||
.High Level REST Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
ElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
----
|
||||
|
||||
<.> an implementation of `ElasticsearchOperations`
|
||||
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
|
||||
<.> 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 ReactiveElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() <.>
|
||||
.connectedTo("localhost:9200")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
|
||||
====
|
||||
|
||||
The following beans can then be injected in other Spring components:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
ReactiveElasticsearchOperations operations; <.>
|
||||
|
||||
@Autowired
|
||||
ReactiveElasticsearchClient elasticsearchClient; <.>
|
||||
|
||||
@Autowired
|
||||
RestClient restClient; <.>
|
||||
----
|
||||
|
||||
the following can be injected:
|
||||
|
||||
<.> an implementation of `ReactiveElasticsearchOperations`
|
||||
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
|
||||
This is a reactive implementation based on the 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)
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The Elasticsearch Java RestHighLevelClient is deprecated, but still can be configured like shown (make sure to read
|
||||
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well).
|
||||
|
||||
It should only be used to access an Elasticsearch cluster running version 7, even with the compatibility headers set
|
||||
there are cases where the `RestHighLevelClient` cannot read the responses sent from a version 8 cluster.
|
||||
====
|
||||
|
||||
.RestHighLevelClient
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.erhlc.AbstractElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
|
||||
@@ -144,6 +84,15 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
RestHighLevelClient highLevelClient;
|
||||
|
||||
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
|
||||
|
||||
// ...
|
||||
|
||||
IndexRequest request = new IndexRequest("spring-data")
|
||||
.id(randomID())
|
||||
.source(singletonMap("feature", "high-level-rest-client"))
|
||||
.setRefreshPolicy(IMMEDIATE);
|
||||
|
||||
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
|
||||
----
|
||||
|
||||
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
|
||||
@@ -152,25 +101,16 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.reactive]]
|
||||
== Reactive Client (deprecated)
|
||||
== Reactive Client
|
||||
|
||||
The `org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchClient` is a non official driver based on `WebClient`.
|
||||
The `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.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
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)
|
||||
.Reactive REST Client
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.erhlc.AbstractReactiveElasticsearchConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class ReactiveRestClientConfig extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@@ -197,6 +137,8 @@ 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
|
||||
|
||||
@@ -206,11 +148,6 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.ElasticsearchClients.*;
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.add("some-header", "on every request") <.>
|
||||
|
||||
@@ -229,7 +166,12 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
return headers;
|
||||
})
|
||||
.withClientConfigurer( <.>
|
||||
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
|
||||
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
|
||||
// ...
|
||||
return webClient;
|
||||
}))
|
||||
.withClientConfigurer( <.>
|
||||
RestClients.RestClientConfigurationCallback.from(clientBuilder -> {
|
||||
// ...
|
||||
return clientBuilder;
|
||||
}))
|
||||
@@ -244,102 +186,51 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
<.> Optionally set a proxy.
|
||||
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
|
||||
<.> Set the connection timeout.
|
||||
Default is 10 sec.
|
||||
<.> Set the socket timeout.
|
||||
Default is 5 sec.
|
||||
<.> Optionally set headers.
|
||||
<.> Add basic authentication.
|
||||
<.> A `Supplier<HttpHeaders>` 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.
|
||||
<.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added
|
||||
multiple times.
|
||||
<.> 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
|
||||
====
|
||||
|
||||
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:
|
||||
|
||||
[[elasticsearch.clients.configuration.callbacks.rest]]
|
||||
==== Configuration of the low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
|
||||
`RestClient`:
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
|
||||
// configure the Elasticsearch RestClient
|
||||
return restClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configurationcallbacks.httpasync]]
|
||||
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
|
||||
|
||||
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpCLient that is
|
||||
used by the `RestClient`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ClientConfiguration.builder()
|
||||
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
|
||||
// configure the HttpAsyncClient
|
||||
return httpAsyncClientBuilder;
|
||||
}))
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.configuration.headers]]
|
||||
=== 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:
|
||||
|
||||
CAUTION: Even when these headers are set, there are cases where the response returned from the cluster cannot be
|
||||
parsed with the client. This is not an error in Spring Data Elasticsearch.
|
||||
When using Spring Data Elasticsearch 4 - which uses the Elasticsearch 7 client libraries - 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 Elasticserach documentation].
|
||||
This should be done using a header supplier like shown above:
|
||||
|
||||
====
|
||||
[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();
|
||||
|
||||
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
|
||||
configurationBuilder //
|
||||
// ...
|
||||
.withHeaders(() -> {
|
||||
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
|
||||
defaultCompatibilityHeaders.add("Accept",
|
||||
"application/vnd.elasticsearch+json;compatible-with=7");
|
||||
defaultCompatibilityHeaders.add("Content-Type",
|
||||
"application/vnd.elasticsearch+json;compatible-with=7");
|
||||
return defaultCompatibilityHeaders;
|
||||
});
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.clients.logging]]
|
||||
== Client Logging
|
||||
|
||||
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level
|
||||
needs to be turned on as outlined in the snippet below. This can be enabled in the Elasticsearch client by setting
|
||||
the level of the `tracer` package to "trace" (see
|
||||
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-usage-logging.html)
|
||||
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below.
|
||||
|
||||
.Enable transport layer logging
|
||||
[source,xml]
|
||||
----
|
||||
<logger name="tracer" level="trace"/>
|
||||
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/>
|
||||
----
|
||||
|
||||
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`, is not available for the `TransportClient`.
|
||||
|
||||
@@ -13,13 +13,7 @@ Spring Data Elasticsearch uses the `EntityCallback` API internally for its audit
|
||||
|
||||
| Reactive/BeforeConvertCallback
|
||||
| `onBeforeConvert(T entity, IndexCoordinates index)`
|
||||
| Invoked before a domain object is converted to `org.springframework.data.elasticsearch.core.document.Document`.
|
||||
Can return the `entity` or a modified entity which then will be converted.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
| Reactive/AfterLoadCallback
|
||||
| `onAfterLoad(Document document, Class<T> type, IndexCoordinates indexCoordinates)`
|
||||
| Invoked after the result from Elasticsearch has been read into a `org.springframework.data.elasticsearch.core.document.Document`.
|
||||
| Invoked before a domain object is converted to `org.springframework.data.elasticsearch.core.document.Document`. Can return the `entity` or a modified entity which then will be converted.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
| Reactive/AfterConvertCallback
|
||||
@@ -38,3 +32,4 @@ Can return the `entity` or a modified entity which then will be converted.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
|===
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
Spring Data Elasticsearch supports the https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html[Join data type] for creating the corresponding index mappings and for storing the relevant information.
|
||||
|
||||
[[elasticsearch.jointype.setting-up]]
|
||||
== Setting up the data
|
||||
|
||||
For an entity to be used in a parent child join relationship, it must have a property of type `JoinField` which must be annotated.
|
||||
@@ -161,7 +160,6 @@ Spring Data Elasticsearch will build the following mapping for this class:
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.jointype.storing]]
|
||||
== Storing data
|
||||
|
||||
Given a repository for this class the following code inserts a question, two answers, a comment and a vote:
|
||||
@@ -211,10 +209,9 @@ void init() {
|
||||
<5> a vote for the first answer, this needs to have the routing set to the weather document, see <<elasticsearch.routing>>.
|
||||
====
|
||||
|
||||
[[elasticsearch.jointype.retrieving]]
|
||||
== Retrieving data
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
@@ -222,17 +219,11 @@ 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();
|
||||
|
||||
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);
|
||||
return operations.search(query, Statement.class);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -95,7 +95,6 @@ default boolean createIndex(String indexName) {
|
||||
[[elasticsearch-migration-guide-3.2-4.0.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
[[elasticsearch-migration-guide-3.2-4.0.deprecations.methods-classes]]
|
||||
=== Methods and classes
|
||||
|
||||
Many functions and classes have been deprecated. These functions still work, but the Javadocs show with what they should be replaced.
|
||||
@@ -116,7 +115,6 @@ Many functions and classes have been deprecated. These functions still work, but
|
||||
<T> T queryForObject(GetQuery query, Class<T> clazz);
|
||||
----
|
||||
|
||||
[[elasticsearch-migration-guide-3.2-4.0.deprecations.elasticsearch]]
|
||||
=== Elasticsearch deprecations
|
||||
|
||||
Since version 7 the Elasticsearch `TransportClient` is deprecated, it will be removed with Elasticsearch version 8. Spring Data Elasticsearch deprecates the `ElasticsearchTemplate` class which uses the `TransportClient` in version 4.0.
|
||||
|
||||
@@ -9,7 +9,7 @@ This section describes breaking changes from version 4.0.x to 4.1.x and how remo
|
||||
.Definition of the id property
|
||||
It is possible to define a property of en entity as the id property by naming it either `id` or `document`.
|
||||
This behaviour is now deprecated and will produce a warning.
|
||||
Please use the `@Id` annotation to mark a property as being the id property.
|
||||
PLease us the `@Id` annotation to mark a property as being the id property.
|
||||
|
||||
.Index mappings
|
||||
In the `ReactiveElasticsearchClient.Indices` interface the `updateMapping` methods are deprecated in favour of the `putMapping` methods.
|
||||
@@ -32,7 +32,6 @@ They had been deprecated in Spring Data Elasticsearch 4.0 and their values weren
|
||||
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes.returntypes-1]]
|
||||
=== Return types of ReactiveElasticsearchClient.Indices methods
|
||||
|
||||
The methods in the `ReactiveElasticsearchClient.Indices` were not used up to now.
|
||||
@@ -41,8 +40,7 @@ With the introduction of the `ReactiveIndexOperations` it became necessary to ch
|
||||
* the `createIndex` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful index creation.
|
||||
* the `updateMapping` variants now return a `Mono<Boolean>` instead of a `Mono<Void>` to signal successful mappings storage.
|
||||
|
||||
[[elasticsearch-migration-guide-4.0-4.1.breaking-changes.returntypes-2]]
|
||||
=== Return types of DocumentOperations.bulkIndex methods
|
||||
=== Return types of DocumentOperartions.bulkIndex methods
|
||||
|
||||
These methods were returning a `List<String>` containing the ids of the new indexed records.
|
||||
These methods were returing a `List<String>` containing the ids of the new indexed records.
|
||||
Now they return a `List<IndexedObjectInformation>`; these objects contain the id and information about optimistic locking (seq_no and primary_term)
|
||||
|
||||
@@ -6,7 +6,6 @@ This section describes breaking changes from version 4.1.x to 4.2.x and how remo
|
||||
[[elasticsearch-migration-guide-4.1-4.2.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.deprecations.document]]
|
||||
=== @Document parameters
|
||||
|
||||
The parameters of the `@Document` annotation that are relevant for the index settings (`useServerConfiguration`, `shards`. `replicas`, `refreshIntervall` and `indexStoretype`) have been moved to the `@Setting` annotation. Use in `@Document` is still possible but deprecated.
|
||||
@@ -15,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.
|
||||
Score values are returned in the `SearchHit` instances that encapsulate the returned entities.
|
||||
Scroe 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`.
|
||||
@@ -29,10 +28,8 @@ The deprecated `find` methods from `ReactiveSearchOperations` and `ReactiveDocum
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy]]
|
||||
=== RefreshPolicy
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.enum]]
|
||||
==== Enum package changed
|
||||
|
||||
It was possible in 4.1 to configure the refresh policy for the `ReactiveElasticsearchTemplate` by overriding the method `AbstractReactiveElasticsearchConfiguration.refreshPolicy()` in a custom configuration class.
|
||||
@@ -41,7 +38,6 @@ The return value of this method was an instance of the class `org.elasticsearch.
|
||||
Now the configuration must return `org.springframework.data.elasticsearch.core.RefreshPolicy`.
|
||||
This enum has the same values and triggers the same behaviour as before, so only the `import` statement has to be adjusted.
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.behaviour]]
|
||||
==== Refresh behaviour
|
||||
|
||||
`ElasticsearchOperations` and `ReactiveElasticsearchOperations` now explicitly use the `RefreshPolicy` set on the template for write requests if not null.
|
||||
@@ -51,21 +47,17 @@ The provided implementations for `ElasticsearchRepository` and `ReactiveElastics
|
||||
This is the same behaviour as in previous versions.
|
||||
If a refresh policy is set, then it will be used by the repositories as well.
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.refresh-policy.configuration]]
|
||||
==== Refresh configuration
|
||||
|
||||
When configuring Spring Data Elasticsearch like described in <<elasticsearch.clients>> by using `ElasticsearchConfigurationSupport`, `AbstractElasticsearchConfiguration` or `AbstractReactiveElasticsearchConfiguration` the refresh policy will be initialized to `null`.
|
||||
Previously the reactive code initialized this to `IMMEDIATE`, now reactive and non-reactive code show the same behaviour.
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types]]
|
||||
=== Method return types
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types.delete]]
|
||||
==== delete methods that take a Query
|
||||
|
||||
The reactive methods previously returned a `Mono<Long>` with the number of deleted documents, the non reactive versions were void. They now return a `Mono<ByQueryResponse>` which contains much more detailed information about the deleted documents and errors that might have occurred.
|
||||
|
||||
[[elasticsearch-migration-guide-4.1-4.2.breaking-changes.method-return-types.multiget]]
|
||||
==== multiget methods
|
||||
|
||||
The implementations of _multiget_ previousl only returned the found entities in a `List<T>` for non-reactive implementations and in a `Flux<T>` for reactive implementations. If the request contained ids that were not found, the information that these are missing was not available. The user needed to compare the returned ids to the requested ones to find
|
||||
|
||||
@@ -21,7 +21,6 @@ Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and
|
||||
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.deprecations.suggest]]
|
||||
=== suggest methods
|
||||
|
||||
In `SearchOperations`, and so in `ElasticsearchOperations` as well, the `suggest` methods taking a `org.elasticsearch.search.suggest.SuggestBuilder` as argument and returning a `org.elasticsearch.action.search.SearchResponse` have been deprecated.
|
||||
@@ -33,7 +32,6 @@ Here as well the old methods are deprecated.
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]]
|
||||
== Breaking Changes
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.1]]
|
||||
=== Removal of `org.elasticsearch` classes from the API.
|
||||
|
||||
* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same.
|
||||
@@ -48,7 +46,6 @@ The same change has been done to the `ReactiveSearchOperations.aggregate()` func
|
||||
Programs using the aggregations need to be changed to cast the returned value to the appropriate class to further proces it.
|
||||
* methods that might have thrown a `org.elasticsearch.ElasticsearchStatusException` now will throw `org.springframework.data.elasticsearch.RestStatusException` instead.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.2]]
|
||||
=== Handling of field and sourceFilter properties of Query
|
||||
|
||||
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`.
|
||||
@@ -56,13 +53,11 @@ This was not correct, as these are different things for Elasticsearch.
|
||||
This has been corrected.
|
||||
As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.3]]
|
||||
=== search_type default value
|
||||
|
||||
The default value for the `search_type` in Elasticsearch is `query_then_fetch`.
|
||||
This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.4]]
|
||||
=== BulkOptions changes
|
||||
|
||||
Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type:
|
||||
@@ -70,17 +65,14 @@ Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOp
|
||||
* the type of the `timeout` property has been changed to `java.time.Duration`.
|
||||
* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.5]]
|
||||
=== IndicesOptions change
|
||||
|
||||
Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.6]]
|
||||
=== Completion classes
|
||||
|
||||
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes.7]]
|
||||
=== Other renamings
|
||||
|
||||
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
[[elasticsearch-migration-guide-4.3-4.4]]
|
||||
= Upgrading from 4.3.x to 4.4.x
|
||||
|
||||
This section describes breaking changes from version 4.3.x to 4.4.x and how removed features can be replaced by new introduced features.
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.deprecations]]
|
||||
== Deprecations
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.deprecations.reactive-operations]]
|
||||
=== 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
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.1]]
|
||||
=== Removal of deprecated classes
|
||||
|
||||
==== `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 `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`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.2]]
|
||||
=== Package changes
|
||||
|
||||
In 4.3 two classes (`ElasticsearchAggregations` and `ElasticsearchAggregation`) had been moved to the `org.springframework.data.elasticsearch.core.clients.elasticsearch7` package in preparation for the integration of the new Elasticsearch client.
|
||||
The were moved back to the `org.springframework.data.elasticsearch.core` package as we keep the classes use the old Elasticsearch client where they were.
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.breaking-changes.3]]
|
||||
=== 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.
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to]]
|
||||
=== 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:
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.not]]
|
||||
==== 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.
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.dependencies]]
|
||||
==== 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>
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration]]
|
||||
==== New configuration classes
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration.imperative]]
|
||||
===== 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`
|
||||
|
||||
[[elasticsearch-migration-guide-4.3-4.4.new-clients.how-to.configuration.reactive]]
|
||||
===== 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`
|
||||
@@ -1,168 +0,0 @@
|
||||
[[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
|
||||
|
||||
=== Custom trace level logging
|
||||
|
||||
Logging by setting the property `logging.level.org.springframework.data.elasticsearch.client.WIRE=trace` is
|
||||
deprecated now, the Elasticsearch `RestClient` provides a better solution that can be activated by setting the
|
||||
logging level of the `tracer` package to "trace".
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-4.5.deprecations.package]]
|
||||
=== `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>>
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-4.5.deprecations.code]]
|
||||
=== 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
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes.deprecated-calls]]
|
||||
=== Removal of deprecated calls
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes.deprecated-calls.1]]
|
||||
==== 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.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-records]]
|
||||
=== 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`
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.breaking-changes-http-headers]]
|
||||
=== 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`.
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.new-clients.imperative]]
|
||||
=== 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`
|
||||
|
||||
[[elasticsearch-migration-guide-4.4-5.0.new-clients.reactive]]
|
||||
=== 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.9</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
----
|
||||
====
|
||||
|
||||
Make sure to specify the version 7.17.9 explicitly, otherwise maven will resolve to 8.5.3, and this does not exist.
|
||||
@@ -87,19 +87,12 @@ private ElasticsearchOperations operations;
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
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();
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFilter(boolFilter().must(termFilter("id", documentId)))
|
||||
.build();
|
||||
|
||||
SearchHits<SampleEntity> sampleEntities = operations.search(query, SampleEntity.class, index);
|
||||
Page<SampleEntity> sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index);
|
||||
----
|
||||
====
|
||||
|
||||
@@ -114,15 +107,13 @@ This is internally used by Spring Data Elasticsearch to provide the implementati
|
||||
----
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
Query searchQuery = NativeQuery.builder()
|
||||
.withQuery(q -> q
|
||||
.matchAll(ma -> ma))
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
SearchHitsIterator<SampleEntity> stream = elasticsearchOperations.searchForStream(searchQuery, SampleEntity.class,
|
||||
index);
|
||||
SearchHitsIterator<SampleEntity> stream = elasticsearchTemplate.searchForStream(searchQuery, SampleEntity.class, index);
|
||||
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
while (stream.hasNext()) {
|
||||
@@ -133,28 +124,23 @@ 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 `AbstractElasticsearchTemplate` can be used (this is the base implementation for the
|
||||
different `ElasticsearchOperations` implementations:
|
||||
There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
|
||||
AbstractElasticsearchTemplate template = (AbstractElasticsearchTemplate)operations;
|
||||
@Autowired ElasticsearchRestTemplate template;
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("sample-index");
|
||||
|
||||
Query query = NativeQuery.builder()
|
||||
.withQuery(q -> q
|
||||
.matchAll(ma -> ma))
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
SearchQuery searchQuery = new NativeSearchQueryBuilder()
|
||||
.withQuery(matchAllQuery())
|
||||
.withFields("message")
|
||||
.withPageable(PageRequest.of(0, 10))
|
||||
.build();
|
||||
|
||||
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, query, SampleEntity.class, index);
|
||||
SearchScrollHits<SampleEntity> scroll = template.searchScrollStart(1000, searchQuery, SampleEntity.class, index);
|
||||
|
||||
String scrollId = scroll.getScrollId();
|
||||
List<SampleEntity> sampleEntities = new ArrayList<>();
|
||||
@@ -204,7 +190,6 @@ Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
|
||||
From version 7.12 on Elasticsearch has added the feature of runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html).
|
||||
Spring Data Elasticsearch supports this in two ways:
|
||||
|
||||
[[elasticsearch.misc.runtime-fields.index-mappings]]
|
||||
=== Runtime field definitions in the index mappings
|
||||
|
||||
The first way to define runtime fields is by adding the definitions to the index mappings (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html).
|
||||
@@ -239,7 +224,6 @@ public class RuntimeFieldEntity {
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.misc.runtime-fields.query]]
|
||||
=== Runtime fields definitions set on a Query
|
||||
|
||||
The second way to define runtime fields is by adding the definitions to a search query (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html).
|
||||
@@ -277,42 +261,3 @@ 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,25 +1,6 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.5-0-1]]
|
||||
== New in Spring Data Elasticsearch 5.0.1
|
||||
|
||||
* Upgrade to Elasticsearch 8.5.3
|
||||
|
||||
[[new-features.5-0-0]]
|
||||
== New in Spring Data Elasticsearch 5.0
|
||||
|
||||
* Upgrade to Java 17 baseline
|
||||
* Upgrade to Spring Framework 6
|
||||
* Upgrade to Elasticsearch 8.5.0
|
||||
* Use the new Elasticsearch client library
|
||||
|
||||
[[new-features.4-4-0]]
|
||||
== New in Spring Data Elasticsearch 4.4
|
||||
|
||||
* Introduction of new imperative and reactive clients using the classes from the new Elasticsearch Java client
|
||||
* Upgrade to Elasticsearch 7.17.3.
|
||||
|
||||
[[new-features.4-3-0]]
|
||||
== New in Spring Data Elasticsearch 4.3
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
[[elasticsearch.mapping]]
|
||||
= Elasticsearch Object Mapping
|
||||
|
||||
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON
|
||||
representation that is stored in Elasticsearch and back. The class that is internally used for this mapping is the
|
||||
`MappingElasticsearcvhConverter`.
|
||||
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
|
||||
|
||||
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>.
|
||||
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
|
||||
|
||||
The main reasons for the removal of the Jackson based mapper are:
|
||||
|
||||
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`.
|
||||
This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
|
||||
* Custom field types and formats also need to be stored into the Elasticsearch index mappings.
|
||||
The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
|
||||
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
|
||||
|
||||
Using the `MappingElasticsearchConverter` now covers all these cases.
|
||||
|
||||
[[elasticsearch.mapping.meta-model]]
|
||||
== Meta Model Object Mapping
|
||||
@@ -20,16 +31,17 @@ The metadata is taken from the entity's properties which can be annotated.
|
||||
The following annotations are available:
|
||||
|
||||
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database.
|
||||
The most important attributes are (check the API documentation for the complete list of attributes):
|
||||
The most important attributes are:
|
||||
** `indexName`: the name of the index to store this entity in.
|
||||
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
|
||||
** `createIndex`: flag whether to create an index on repository bootstrapping.
|
||||
Default value is _true_.
|
||||
See <<elasticsearch.repositories.autocreation>>
|
||||
|
||||
** `versionType`: Configuration of version management.
|
||||
Default value is _EXTERNAL_.
|
||||
|
||||
* `@Id`: Applied at the field level to mark the field used for identity purpose.
|
||||
* `@Transient`, `@ReadOnlyProperty`, `@WriteOnlyProperty`: see the following section <<elasticsearch.mapping.meta-model.annotations.read-write>> for detailed information.
|
||||
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
|
||||
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database.
|
||||
Constructor arguments are mapped by name to the key values in the retrieved Document.
|
||||
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
|
||||
@@ -38,8 +50,8 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu
|
||||
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types].
|
||||
If the field type is not specified, it defaults to `FieldType.Auto`.
|
||||
This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored (check the Elasticsearch documentation for dynamic mapping rules).
|
||||
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.annotations.date-formats>>.
|
||||
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.annotations.date-formats>>.
|
||||
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
|
||||
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
|
||||
** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_.
|
||||
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
|
||||
* `@GeoPoint`: Marks a field as _geo_point_ datatype.
|
||||
@@ -49,26 +61,7 @@ In difference to a registered Spring `Converter` this only converts the annotate
|
||||
|
||||
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations.read-write]]
|
||||
==== Controlling which properties are written to and read from Elasticsearch
|
||||
|
||||
This section details the annotations that define if the value of a property is written to or
|
||||
read from Elasticsearch.
|
||||
|
||||
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be
|
||||
sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the
|
||||
resulting entity.
|
||||
|
||||
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when
|
||||
returning data, the proeprty will be filled with the value returned in the document from Elasticsearch. One use case
|
||||
for this are runtime fields defined in the index mapping.
|
||||
|
||||
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set
|
||||
with any value when reading document. This can be used for example for synthesized fields which should go into the
|
||||
Elasticsearch index but are not used elsewhere.
|
||||
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations.date-formats]]
|
||||
[[elasticsearch.mapping.meta-model.date-formats]]
|
||||
==== Date format mapping
|
||||
|
||||
Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type.
|
||||
@@ -110,10 +103,6 @@ The following table shows the different attributes and the mapping created from
|
||||
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
|
||||
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
|
||||
|
||||
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of
|
||||
predefined values and their patterns.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations.range]]
|
||||
==== Range types
|
||||
|
||||
When a field is annotated with a type of one of _Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,_ or _Ip_Range_ the field must be an instance of a class that will be mapped to an Elasticsearch range, for example:
|
||||
@@ -159,7 +148,6 @@ class SomePersonData {
|
||||
Supported classes for the type `<T>` are `Integer`, `Long`, `Float`, `Double`, `Date` and classes that implement the
|
||||
`TemporalAccessor` interface.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations.mapped-names]]
|
||||
==== Mapped field names
|
||||
|
||||
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch.
|
||||
@@ -169,31 +157,9 @@ It is also possible to define a `FieldNamingStrategy` in the configuration of th
|
||||
If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch.
|
||||
A `FieldNamingStrategy` applies to all entities; it can be overwritten by setting a specific name with `@Field` on a property.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.annotations.non-field-backed-properties]]
|
||||
==== Non-field-backed properties
|
||||
|
||||
Normally the properties used in an entity are fields of the entity class. There might be cases, when a property value
|
||||
is calculated in the entity and should be stored in Elasticsearch. In this case, the getter method (`getProperty()`) can be
|
||||
annotated
|
||||
with the `@Field` annotation, in addition to that the method must be annotated with `@AccessType(AccessType.Type
|
||||
.PROPERTY)`. The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only
|
||||
written to Elasticsearch. A full example:
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Field(type = Keyword)
|
||||
@WriteOnlyProperty
|
||||
@AccessType(AccessType.Type.PROPERTY)
|
||||
public String getProperty() {
|
||||
return "some value that is calculated here";
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules]]
|
||||
=== Mapping Rules
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules.typehints]]
|
||||
==== Type Hints
|
||||
|
||||
Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping.
|
||||
@@ -204,6 +170,7 @@ Those type hints are represented as `_class` attributes within the document and
|
||||
[source,java]
|
||||
----
|
||||
public class Person { <1>
|
||||
|
||||
@Id String id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
@@ -260,7 +227,7 @@ In this case, writing the type hint will produce an error, as the field cannot b
|
||||
|
||||
Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <<elasticsearch.clients>>).
|
||||
|
||||
As an alternative they can be disabled for a single index with the `@Document` annotation:
|
||||
As an alternativ they can be disabled for a single index with the `@Document` annotation:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
@@ -273,7 +240,6 @@ WARNING: We strongly advise against disabling Type Hints.
|
||||
Only do this if you are forced to.
|
||||
Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely.
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules.geospatial]]
|
||||
==== Geospatial Types
|
||||
|
||||
Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
|
||||
@@ -283,6 +249,7 @@ Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
|
||||
[source,java]
|
||||
----
|
||||
public class Address {
|
||||
|
||||
String city, street;
|
||||
Point location;
|
||||
}
|
||||
@@ -298,7 +265,6 @@ public class Address {
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules.geojson]]
|
||||
==== GeoJson Types
|
||||
|
||||
Spring Data Elasticsearch supports the GeoJson types by providing an interface `GeoJson` and implementations for the different geometries.
|
||||
@@ -339,7 +305,6 @@ The following GeoJson types are implemented:
|
||||
* `GeoJsonMultiPolygon`
|
||||
* `GeoJsonGeometryCollection`
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules.collections]]
|
||||
==== Collections
|
||||
|
||||
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
|
||||
@@ -367,7 +332,6 @@ public class Person {
|
||||
----
|
||||
====
|
||||
|
||||
[[elasticsearch.mapping.meta-model.rules.maps]]
|
||||
==== Maps
|
||||
|
||||
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
|
||||
|
||||
@@ -30,11 +30,71 @@ There is support for automatic creation of indices and writing the mappings when
|
||||
|
||||
====
|
||||
|
||||
[[elasticsearch.operations.template]]
|
||||
== ElasticsearchTemplate
|
||||
|
||||
NOTE: Usage of the ElasticsearchTemplate is deprecated as of version 4.0, use ElasticsearchRestTemplate instead.
|
||||
|
||||
The `ElasticsearchTemplate` is an implementation of the `ElasticsearchOperations` interface using the <<elasticsearch.clients.transport>>.
|
||||
|
||||
.ElasticsearchTemplate configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class TransportClientConfig extends ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public Client elasticsearchClient() throws UnknownHostException { <1>
|
||||
Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();
|
||||
TransportClient client = new PreBuiltTransportClient(settings);
|
||||
client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
|
||||
public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { <2>
|
||||
return new ElasticsearchTemplate(elasticsearchClient());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> Setting up the <<elasticsearch.clients.transport>>.
|
||||
Deprecated as of version 4.0.
|
||||
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
|
||||
====
|
||||
|
||||
[[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 example assumes that `Person` is a class that is annotated with `@Document`, `@Id` etc (see <<elasticsearch.mapping.meta-model.annotations>>).
|
||||
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
|
||||
====
|
||||
[source,java]
|
||||
@@ -45,29 +105,34 @@ public class TestController {
|
||||
|
||||
private ElasticsearchOperations elasticsearchOperations;
|
||||
|
||||
public TestController(ElasticsearchOperations elasticsearchOperations) { <.>
|
||||
public TestController(ElasticsearchOperations elasticsearchOperations) { <1>
|
||||
this.elasticsearchOperations = elasticsearchOperations;
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public String save(@RequestBody Person person) { <.>
|
||||
Person savedEntity = elasticsearchOperations.save(person);
|
||||
return savedEntity.getId();
|
||||
public String save(@RequestBody Person person) { <2>
|
||||
|
||||
IndexQuery indexQuery = new IndexQueryBuilder()
|
||||
.withId(person.getId().toString())
|
||||
.withObject(person)
|
||||
.build();
|
||||
String documentId = elasticsearchOperations.index(indexQuery);
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Person findById(@PathVariable("id") Long id) { <.>
|
||||
Person person = elasticsearchOperations.get(id.toString(), Person.class);
|
||||
public Person findById(@PathVariable("id") Long id) { <3>
|
||||
Person person = elasticsearchOperations
|
||||
.queryForObject(GetQuery.getById(id.toString()), Person.class);
|
||||
return person;
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
<.> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
|
||||
<.> Store some entity in the Elasticsearch cluster. The id is read from the returned entity, as it might have been
|
||||
null in the `person` object and been created by Elasticsearch.
|
||||
<.> Retrieve the entity with a get by id.
|
||||
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
|
||||
<2> Store some entity in the Elasticsearch cluster.
|
||||
<3> Retrieve the entity with a query by id.
|
||||
====
|
||||
|
||||
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.
|
||||
@@ -113,13 +178,10 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
|
||||
.SearchHitsIterator<T>
|
||||
An Iterator returned by the streaming functions of the `SearchOperations` interface.
|
||||
|
||||
.ReactiveSearchHits
|
||||
`ReactiveSearchOperations` has methods returning a `Mono<ReactiveSearchHits<T>>`, this contains the same information as a `SearchHits<T>` object, but will provide the contained `SearchHit<T>` objects as a `Flux<SearchHit<T>>` and not as a list.
|
||||
|
||||
[[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 `NativeQuery`.
|
||||
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`.
|
||||
|
||||
[[elasticsearch.operations.criteriaquery]]
|
||||
=== CriteriaQuery
|
||||
@@ -199,7 +261,7 @@ The following code shows a query that searches for persons having the first name
|
||||
[source,java]
|
||||
----
|
||||
|
||||
Query query = new StringQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
|
||||
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
|
||||
----
|
||||
@@ -207,28 +269,21 @@ SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
|
||||
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
|
||||
|
||||
[[elasticsearch.operations.nativequery]]
|
||||
=== NativeQuery
|
||||
[[elasticsearch.operations.nativesearchquery]]
|
||||
=== NativeSearchQuery
|
||||
|
||||
`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".
|
||||
`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".
|
||||
|
||||
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 = 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();
|
||||
Query query = new NativeSearchQueryBuilder()
|
||||
.addAggregation(terms("lastnames").field("lastname").size(10)) //
|
||||
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
|
||||
.build();
|
||||
|
||||
SearchHits<Person> searchHits = operations.search(query, Person.class);
|
||||
----
|
||||
|
||||
@@ -40,7 +40,6 @@ include::reactive-elasticsearch-repositories.adoc[leveloffset=+1]
|
||||
[[elasticsearch.repositories.annotations]]
|
||||
== Annotations for repository methods
|
||||
|
||||
[[elasticsearch.repositories.annotations.highlight]]
|
||||
=== @Highlight
|
||||
|
||||
The `@Highlight` annotation on a repository method defines for which fields of the returned entity highlighting should be included. To search for some text in a `Book` 's name or summary and have the found data highlighted, the following repository method can be used:
|
||||
@@ -54,7 +53,7 @@ interface BookRepository extends Repository<Book, String> {
|
||||
@HighlightField(name = "name"),
|
||||
@HighlightField(name = "summary")
|
||||
})
|
||||
SearchHits<Book> findByNameOrSummary(String text, String summary);
|
||||
List<SearchHit<Book>> findByNameOrSummary(String text, String summary);
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -63,31 +62,6 @@ 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.
|
||||
|
||||
[[elasticsearch.repositories.annotations.sourcefilters]]
|
||||
=== @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
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
The Elasticsearch module supports all basic query building feature as string queries, native search queries, criteria based queries or have it being derived from the method name.
|
||||
|
||||
[[elasticsearch.query-methods.finders.declared]]
|
||||
=== Declared queries
|
||||
|
||||
Deriving the query from the method name is not always sufficient and/or may result in unreadable method names.
|
||||
@@ -299,7 +298,6 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
|
||||
Use `ElasticsearchOperations` with `CriteriaQuery` in a custom repository implementation if you need to have such a function in a repository.
|
||||
|
||||
[[elasticsearch.query-methods.return-types]]
|
||||
== Method return types
|
||||
|
||||
Repository methods can be defined to have the following return types for returning multiple Elements:
|
||||
@@ -314,9 +312,8 @@ Repository methods can be defined to have the following return types for returni
|
||||
[[elasticsearch.query-methods.at-query]]
|
||||
== Using @Query Annotation
|
||||
|
||||
.Declare query on the method using the `@Query` annotation.
|
||||
.Declare query at 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> {
|
||||
@@ -341,23 +338,3 @@ 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
[[elasticsearch.routing]]
|
||||
= Routing values
|
||||
|
||||
@@ -7,7 +8,6 @@ For this Elasticsearch offers the possibility to define a routing, which is the
|
||||
|
||||
Spring Data Elasticsearch supports routing definitions on storing and retrieving data in the following ways:
|
||||
|
||||
[[elasticsearch.routing.join-types]]
|
||||
== Routing on join-types
|
||||
|
||||
When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearch will automatically use the `parent` property of the entity's `JoinField` property as the value for the routing.
|
||||
@@ -15,7 +15,6 @@ When using join-types (see <<elasticsearch.jointype>>), Spring Data Elasticsearc
|
||||
This is correct for all the use-cases where the parent-child relationship has just one level.
|
||||
If it is deeper, like a child-parent-grandparent relationship - like in the above example from _vote_ -> _answer_ -> _question_ - then the routing needs to explicitly specified by using the techniques described in the next section (the _vote_ needs the _question.id_ as routing value).
|
||||
|
||||
[[elasticsearch.routing.custom]]
|
||||
== Custom routing values
|
||||
|
||||
To define a custom routing for an entity, Spring Data Elasticsearch provides a `@Routing` annotation (reusing the `Statement` class from above):
|
||||
@@ -104,3 +103,4 @@ operations.withRouting(RoutingResolver.just(routing)).delete(id);
|
||||
----
|
||||
<.> `RoutingResolver.just(s)` returns a resolver that will just return the given String.
|
||||
====
|
||||
|
||||
|
||||
@@ -10,9 +10,4 @@ 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
|
||||
|
||||
@@ -5,22 +5,80 @@
|
||||
|
||||
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
|
||||
|
||||
[[elasticsearch.reactive.operations]]
|
||||
== Reactive Elasticsearch Operations
|
||||
[[elasticsearch.reactive.template]]
|
||||
== Reactive Elasticsearch Template
|
||||
|
||||
To get started the `ReactiveElasticsearchOperations` needs to know about the actual client to work with.
|
||||
Please see <<elasticsearch.clients.reactiverestclient>> for details on the client and how to configure it.
|
||||
To get started the `ReactiveElasticsearchTemplate` needs to know about the actual client to work with.
|
||||
Please see <<elasticsearch.clients.reactive>> for details on the client.
|
||||
|
||||
[[elasticsearch.reactive.operations.usage]]
|
||||
=== Reactive Operations Usage
|
||||
[[elasticsearch.reactive.template.configuration]]
|
||||
=== Reactive Template Configuration
|
||||
|
||||
`ReactiveElasticsearchOperations` lets you save, find and delete your domain objects and map those objects to documents
|
||||
stored
|
||||
in Elasticsearch.
|
||||
The easiest way of setting up the `ReactiveElasticsearchTemplate` is via `AbstractReactiveElasticsearchConfiguration` providing
|
||||
dedicated configuration method hooks for `base package`, the `initial entity set` etc.
|
||||
|
||||
.The AbstractReactiveElasticsearchConfiguration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@Bean <1>
|
||||
@Override
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
|
||||
====
|
||||
|
||||
NOTE: If applicable set default `HttpHeaders` via the `ClientConfiguration` of the `ReactiveElasticsearchClient`. See <<elasticsearch.clients.configuration>>.
|
||||
|
||||
TIP: If needed the `ReactiveElasticsearchTemplate` can be configured with default `RefreshPolicy` and `IndicesOptions` that get applied to the related requests by overriding the defaults of `refreshPolicy()` and `indicesOptions()`.
|
||||
|
||||
However one might want to be more in control over the actual components and use a more verbose approach.
|
||||
|
||||
.Configure the ReactiveElasticsearchTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config {
|
||||
|
||||
@Bean <1>
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
// ...
|
||||
}
|
||||
@Bean <2>
|
||||
public ElasticsearchConverter elasticsearchConverter() {
|
||||
return new MappingElasticsearchConverter(elasticsearchMappingContext());
|
||||
}
|
||||
@Bean <3>
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
return new SimpleElasticsearchMappingContext();
|
||||
}
|
||||
@Bean <4>
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
|
||||
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
|
||||
<2> Set up the `ElasticsearchConverter` used for domain type mapping utilizing metadata provided by the mapping context.
|
||||
<3> The Elasticsearch specific mapping context for domain type metadata.
|
||||
<4> The actual template based on the client and conversion infrastructure.
|
||||
====
|
||||
|
||||
[[elasticsearch.reactive.template.usage]]
|
||||
=== Reactive Template Usage
|
||||
|
||||
`ReactiveElasticsearchTemplate` lets you save, find and delete your domain objects and map those objects to documents stored in Elasticsearch.
|
||||
|
||||
Consider the following:
|
||||
|
||||
.Use the ReactiveElasticsearchOperations
|
||||
.Use the ReactiveElasticsearchTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@@ -36,20 +94,15 @@ public class Person {
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
ReactiveELasticsearchOperations operations; <.>
|
||||
|
||||
// ...
|
||||
|
||||
operations.save(new Person("Bruce Banner", 42)) <.>
|
||||
template.save(new Person("Bruce Banner", 42)) <1>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(person -> operations.get(person.id, Person.class)) <.>
|
||||
.flatMap(person -> template.findById(person.id, Person.class)) <2>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(person -> operations.delete(person)) <.>
|
||||
.flatMap(person -> template.delete(person)) <3>
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(id -> operations.count(Person.class)) <.>
|
||||
.flatMap(id -> template.count(Person.class)) <4>
|
||||
.doOnNext(System.out::println)
|
||||
.subscribe(); <.>
|
||||
.subscribe(); <5>
|
||||
----
|
||||
|
||||
The above outputs the following sequence on the console.
|
||||
@@ -61,10 +114,11 @@ The above outputs the following sequence on the console.
|
||||
> QjWCWWcBXiLAnp77ksfR
|
||||
> 0
|
||||
----
|
||||
<.> Insert a new `Person` document into the _marvel_ index . The `id` is generated on server
|
||||
side and set into the instance returned.
|
||||
<.> Lookup the `Person` with matching `id` in the _marvel_ index.
|
||||
<.> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index.
|
||||
<.> Count the total number of documents in the _marvel_ index.
|
||||
<.> Don't forget to _subscribe()_.
|
||||
<1> Insert a new `Person` document into the _marvel_ index under type _characters_. The `id` is generated on server side and set into the instance returned.
|
||||
<2> Lookup the `Person` with matching `id` in the _marvel_ index under type _characters_.
|
||||
<3> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index under type _characters_.
|
||||
<4> Count the total number of documents in the _marvel_ index under type _characters_.
|
||||
<5> Don't forget to _subscribe()_.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Str
|
||||
parameters.
|
||||
<9> Count all entities with matching `firstname`.
|
||||
<10> Check if at least one entity with matching `firstname` exists.
|
||||
<11> Delete all entities with matching `firstname`.
|
||||
<11> Delete all entites with matching `firstname`.
|
||||
====
|
||||
|
||||
[[elasticsearch.reactive.repositories.configuration]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
@@ -25,14 +25,6 @@ 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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
|
||||
+3
-41
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.springframework.data.elasticsearch;
|
||||
|
||||
import org.springframework.dao.UncategorizedDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
@@ -24,48 +23,11 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
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) {
|
||||
this(msg, null);
|
||||
super(msg, null);
|
||||
}
|
||||
|
||||
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) {
|
||||
public UncategorizedElasticsearchException(String msg, 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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
* Copyright 2014-2021 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.
|
||||
@@ -16,11 +16,9 @@
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* 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>.
|
||||
* 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.
|
||||
*
|
||||
* @author Jakub Vavrik
|
||||
* @author Tim te Beek
|
||||
@@ -28,6 +26,19 @@ 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"), //
|
||||
@@ -49,7 +60,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 its implementation
|
||||
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation
|
||||
epoch_millis("epoch_millis"), //
|
||||
epoch_second("epoch_second"), //
|
||||
hour("HH"), //
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -53,6 +53,49 @@ 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.
|
||||
*/
|
||||
@@ -81,22 +124,6 @@ public @interface Document {
|
||||
* @since 4.3
|
||||
*/
|
||||
enum VersionType {
|
||||
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;
|
||||
}
|
||||
INTERNAL, EXTERNAL, EXTERNAL_GTE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
@@ -46,7 +46,7 @@ public enum Dynamic {
|
||||
/**
|
||||
* Inherit the dynamic setting from their parent object or from the mapping type.
|
||||
*/
|
||||
INHERIT("inherit");
|
||||
INHERIT("nherit");
|
||||
|
||||
private final String mappedName;
|
||||
|
||||
|
||||
+12
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
@@ -22,14 +22,19 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a property that will be written to Elasticsearch, but not set when reading from Elasticsearch.
|
||||
* This is needed for synthesized fields that may be used for search but that are not available in the entity.
|
||||
*
|
||||
* Annotation to set the dynamic mapping mode
|
||||
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.FIELD })
|
||||
@Target({ ElementType.TYPE, ElementType.FIELD })
|
||||
@Documented
|
||||
public @interface WriteOnlyProperty {
|
||||
@Deprecated
|
||||
public @interface DynamicMapping {
|
||||
|
||||
DynamicMappingValue value() default DynamicMappingValue.True;
|
||||
}
|
||||
+15
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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,20 +13,27 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch;
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* TODO remove when the new Elasticsearch client is fully working and the old client is removed
|
||||
* values for the {@link DynamicMapping annotation}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
|
||||
*/
|
||||
public interface NewElasticsearchClientDevelopment {
|
||||
@Deprecated
|
||||
public enum DynamicMappingValue {
|
||||
True("true"), False("false"), Strict("strict");
|
||||
|
||||
default boolean newElasticsearchClient() {
|
||||
return false;
|
||||
private final String mappedName;
|
||||
|
||||
DynamicMappingValue(String mappedName) {
|
||||
this.mappedName = mappedName;
|
||||
}
|
||||
|
||||
default boolean oldElasticsearchClient() {
|
||||
return !newElasticsearchClient();
|
||||
public String getMappedName() {
|
||||
return mappedName;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -39,7 +39,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
|
||||
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface Field {
|
||||
@@ -53,8 +53,9 @@ public @interface Field {
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2023 the original author or authors.
|
||||
* Copyright 2017-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
* Copyright 2014-2021 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.
|
||||
@@ -128,7 +128,7 @@ public @interface InnerField {
|
||||
|
||||
/**
|
||||
* to be used in combination with {@link FieldType#Rank_Feature}
|
||||
*
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
boolean positiveScoreImpact() default true;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
* Copyright 2014-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
* Copyright 2014-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2023 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,14 +15,8 @@
|
||||
*/
|
||||
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,23 +28,22 @@ import org.springframework.data.annotation.QueryAnnotation;
|
||||
*/
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Documented
|
||||
@QueryAnnotation
|
||||
public @interface Query {
|
||||
|
||||
/**
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for query.
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0
|
||||
*/
|
||||
@AliasFor("query")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* @return Elasticsearch query to be used when executing query. May contain placeholders eg. ?0. Alias for value
|
||||
* @since 5.0
|
||||
* Named Query Named looked up by repository.
|
||||
*
|
||||
* @deprecated since 4.2, not implemented and used anywhere
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String query() default "";
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Returns whether the query defined should be executed as count projection.
|
||||
@@ -60,3 +53,4 @@ public @interface Query {
|
||||
*/
|
||||
boolean count() default false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
* Copyright 2014-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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 "";
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2023 the original author or authors.
|
||||
* Copyright 2019-2021 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021-2023 the original author or authors.
|
||||
* Copyright 2021 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.
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.aot;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.util.ReactiveWrappers;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0.1
|
||||
*/
|
||||
public class ElasticsearchAotPredicates {
|
||||
|
||||
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
|
||||
lib) -> ReactiveWrappers.isAvailable(lib);
|
||||
|
||||
public static boolean isReactorPresent() {
|
||||
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
|
||||
}
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.aot;
|
||||
|
||||
import static org.springframework.data.elasticsearch.aot.ElasticsearchAotPredicates.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.data.elasticsearch.client.elc.EntityAsMap;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterLoadCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.AfterSaveCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.BeforeConvertCallback;
|
||||
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.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.data.elasticsearch.core.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 5.0.1
|
||||
*/
|
||||
public class ElasticsearchRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
hints.reflection().registerTypes( //
|
||||
Arrays.asList( //
|
||||
TypeReference.of(AfterConvertCallback.class), //
|
||||
TypeReference.of(AfterLoadCallback.class), //
|
||||
TypeReference.of(AfterSaveCallback.class), //
|
||||
TypeReference.of(BeforeConvertCallback.class), //
|
||||
TypeReference.of(EntityAsMap.class) //
|
||||
), //
|
||||
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
|
||||
if (isReactorPresent()) {
|
||||
hints.reflection().registerTypes( //
|
||||
Arrays.asList( //
|
||||
TypeReference.of(ReactiveAfterConvertCallback.class), //
|
||||
TypeReference.of(ReactiveAfterLoadCallback.class), //
|
||||
TypeReference.of(ReactiveAfterSaveCallback.class), //
|
||||
TypeReference.of(ReactiveBeforeConvertCallback.class) //
|
||||
), //
|
||||
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
// properties needed to log the different versions
|
||||
hints.resources().registerPattern("versions.properties");
|
||||
hints.resources().registerPattern("co/elastic/clients/version.properties");
|
||||
}
|
||||
}
|
||||
+42
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -26,7 +26,9 @@ import java.util.function.Supplier;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -52,6 +54,7 @@ public interface ClientConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to localhost.
|
||||
* <p/>
|
||||
*
|
||||
* <pre class="code">
|
||||
* // "localhost:9200"
|
||||
@@ -66,8 +69,9 @@ public interface ClientConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}. For
|
||||
* example given the endpoint http://localhost:9200
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@code hostAndPort}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration.create("localhost:9200");
|
||||
@@ -80,8 +84,9 @@ public interface ClientConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}. For
|
||||
* example given the endpoint http://localhost:9200
|
||||
* Creates a new {@link ClientConfiguration} instance configured to a single host given {@link InetSocketAddress}.
|
||||
* <p/>
|
||||
* For example given the endpoint http://localhost:9200
|
||||
*
|
||||
* <pre class="code">
|
||||
* ClientConfiguration configuration = ClientConfiguration
|
||||
@@ -168,6 +173,14 @@ 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
|
||||
@@ -336,6 +349,29 @@ 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.
|
||||
*
|
||||
|
||||
+35
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -30,7 +31,8 @@ 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.support.HttpHeaders;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -49,7 +51,7 @@ class ClientConfigurationBuilder
|
||||
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
|
||||
|
||||
private final List<InetSocketAddress> hosts = new ArrayList<>();
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private HttpHeaders headers = HttpHeaders.EMPTY;
|
||||
private boolean useSsl;
|
||||
private @Nullable SSLContext sslContext;
|
||||
private @Nullable HostnameVerifier hostnameVerifier;
|
||||
@@ -59,10 +61,10 @@ class ClientConfigurationBuilder
|
||||
private @Nullable String password;
|
||||
private @Nullable String pathPrefix;
|
||||
private @Nullable String proxy;
|
||||
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<>();
|
||||
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<>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -73,7 +75,7 @@ class ClientConfigurationBuilder
|
||||
|
||||
Assert.notEmpty(hostAndPorts, "At least one host is required");
|
||||
|
||||
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).toList());
|
||||
this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -199,6 +201,28 @@ 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) {
|
||||
@@ -226,6 +250,9 @@ class ClientConfigurationBuilder
|
||||
public ClientConfiguration build() {
|
||||
|
||||
if (username != null && password != null) {
|
||||
if (HttpHeaders.EMPTY.equals(headers)) {
|
||||
headers = new HttpHeaders();
|
||||
}
|
||||
headers.setBasicAuth(username, password);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -17,26 +17,26 @@ package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Logging Utility to log client requests and responses. Logs client requests and responses to Elasticsearch to a
|
||||
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on trace level.
|
||||
* dedicated logger: {@code org.springframework.data.elasticsearch.client.WIRE} on {@link org.slf4j.event.Level#TRACE}
|
||||
* level.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0, Elasticsearch's RestClient has a trace level logging available.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class ClientLogger {
|
||||
|
||||
private static final Log WIRE_LOGGER = LogFactory.getLog("org.springframework.data.elasticsearch.client.WIRE");
|
||||
private static final String lineSeparator = System.getProperty("line.separator");
|
||||
private static final Logger WIRE_LOGGER = LoggerFactory
|
||||
.getLogger("org.springframework.data.elasticsearch.client.WIRE");
|
||||
|
||||
private ClientLogger() {}
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class ClientLogger {
|
||||
/**
|
||||
* Log an outgoing HTTP request.
|
||||
*
|
||||
* @param logId the correlation id, see {@link #newLogId()}.
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param method HTTP method
|
||||
* @param endpoint URI
|
||||
* @param parameters optional parameters.
|
||||
@@ -60,33 +60,16 @@ public abstract class ClientLogger {
|
||||
public static void logRequest(String logId, String method, String endpoint, Object parameters) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Sending request %s %s with parameters: %s", logId, method.toUpperCase(),
|
||||
endpoint, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}", logId, method.toUpperCase(), endpoint,
|
||||
parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an outgoing HTTP request with a request body.
|
||||
*
|
||||
* @param logId the correlation id, see {@link #newLogId()}.
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param method HTTP method
|
||||
* @param endpoint URI
|
||||
* @param parameters optional parameters.
|
||||
@@ -96,93 +79,43 @@ public abstract class ClientLogger {
|
||||
Supplier<Object> body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
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()));
|
||||
WIRE_LOGGER.trace("[{}] Sending request {} {} with parameters: {}{}Request body: {}", logId, method.toUpperCase(),
|
||||
endpoint, parameters, lineSeparator, body.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a raw HTTP response without logging the body.
|
||||
*
|
||||
* @param logId the correlation id, see {@link #newLogId()}.
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param statusCode the HTTP status code.
|
||||
*/
|
||||
public static void logRawResponse(String logId, @Nullable Integer statusCode) {
|
||||
public static void logRawResponse(String logId, @Nullable HttpStatus statusCode) {
|
||||
|
||||
if (isEnabled()) {
|
||||
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));
|
||||
WIRE_LOGGER.trace("[{}] Received raw response: {}", logId, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a raw HTTP response along with the body.
|
||||
*
|
||||
* @param logId the correlation id, see {@link #newLogId()}.
|
||||
* @param logId the correlation Id, see {@link #newLogId()}.
|
||||
* @param statusCode the HTTP status code.
|
||||
* @param body body content.
|
||||
*/
|
||||
public static void logResponse(String logId, Integer statusCode, String body) {
|
||||
public static void logResponse(String logId, HttpStatus statusCode, String body) {
|
||||
|
||||
if (isEnabled()) {
|
||||
WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nResponse body: %s", logId, statusCode, body));
|
||||
WIRE_LOGGER.trace("[{}] Received response: {}{}Response body: {}", logId, statusCode, lineSeparator, body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a raw HTTP response along with the body.
|
||||
* Creates a new, unique correlation Id to improve tracing across log events.
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, unique correlation id to improve tracing across log events.
|
||||
*
|
||||
* @return a new, unique correlation id.
|
||||
* @return a new, unique correlation Id.
|
||||
*/
|
||||
public static String newLogId() {
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2018-2021 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;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to represent a list of cluster nodes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 3.1
|
||||
* @deprecated only used in {@link TransportClientFactoryBean}.
|
||||
*/
|
||||
@SuppressWarnings("DeprecatedIsStillUsed")
|
||||
@Deprecated
|
||||
class ClusterNodes implements Streamable<TransportAddress> {
|
||||
|
||||
public static ClusterNodes DEFAULT = ClusterNodes.of("127.0.0.1:9300");
|
||||
|
||||
private static final String COLON = ":";
|
||||
private static final String COMMA = ",";
|
||||
|
||||
private final List<TransportAddress> clusterNodes;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClusterNodes} by parsing the given source.
|
||||
*
|
||||
* @param source must not be {@literal null} or empty.
|
||||
*/
|
||||
private ClusterNodes(String source) {
|
||||
|
||||
Assert.hasText(source, "Cluster nodes source must not be null or empty!");
|
||||
|
||||
String[] nodes = StringUtils.delimitedListToStringArray(source, COMMA);
|
||||
|
||||
this.clusterNodes = Arrays.stream(nodes).map(node -> {
|
||||
|
||||
String[] segments = StringUtils.delimitedListToStringArray(node, COLON);
|
||||
|
||||
Assert.isTrue(segments.length == 2,
|
||||
() -> String.format("Invalid cluster node %s in %s! Must be in the format host:port!", node, source));
|
||||
|
||||
String host = segments[0].trim();
|
||||
String port = segments[1].trim();
|
||||
|
||||
Assert.hasText(host, () -> String.format("No host name given cluster node %s!", node));
|
||||
Assert.hasText(port, () -> String.format("No port given in cluster node %s!", node));
|
||||
|
||||
return new TransportAddress(toInetAddress(host), Integer.parseInt(port));
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ClusterNodes} by parsing the given source. The expected format is a comma separated list of
|
||||
* host-port-combinations separated by a colon: {@code host:port,host:port,…}.
|
||||
*
|
||||
* @param source must not be {@literal null} or empty.
|
||||
*/
|
||||
public static ClusterNodes of(String source) {
|
||||
return new ClusterNodes(source);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<TransportAddress> iterator() {
|
||||
return clusterNodes.iterator();
|
||||
}
|
||||
|
||||
private static InetAddress toInetAddress(String host) {
|
||||
|
||||
try {
|
||||
return InetAddress.getByName(host);
|
||||
} catch (UnknownHostException o_O) {
|
||||
throw new IllegalArgumentException(o_O);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -17,6 +17,8 @@ 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;
|
||||
@@ -26,7 +28,7 @@ import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@@ -61,8 +63,8 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
|
||||
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
|
||||
|
||||
this.hosts = List.copyOf(hosts);
|
||||
this.headers = headers;
|
||||
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
|
||||
this.headers = new HttpHeaders(headers);
|
||||
this.useSsl = useSsl;
|
||||
this.sslContext = sslContext;
|
||||
this.soTimeout = soTimeout;
|
||||
@@ -127,6 +129,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
|
||||
return webClientConfigurer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClientConfigCallback getHttpClientConfigurer() {
|
||||
return httpClientConfigurer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
|
||||
return clientConfigurers;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
@@ -26,7 +26,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public class InetSocketAddressParser {
|
||||
class InetSocketAddressParser {
|
||||
|
||||
/**
|
||||
* Parse a host and port string into a {@link InetSocketAddress}.
|
||||
@@ -36,7 +36,7 @@ public class InetSocketAddressParser {
|
||||
* @return a {@link InetSocketAddress} that is unresolved to avoid DNS lookups.
|
||||
* @see InetSocketAddress#createUnresolved(String, int)
|
||||
*/
|
||||
public static InetSocketAddress parse(String hostPortString, int defaultPort) {
|
||||
static InetSocketAddress parse(String hostPortString, int defaultPort) {
|
||||
|
||||
Assert.notNull(hostPortString, "HostPortString must not be null");
|
||||
String host;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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.
|
||||
|
||||
+7
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
|
||||
@@ -31,17 +31,14 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* RestHighLevelClientFactoryBean
|
||||
* RestClientFactoryBean
|
||||
*
|
||||
* @author Don Wellington
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class RestHighLevelClientFactoryBean
|
||||
implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
|
||||
public class RestClientFactoryBean implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(RestHighLevelClientFactoryBean.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RestClientFactoryBean.class);
|
||||
|
||||
private @Nullable RestHighLevelClient client;
|
||||
private String hosts = "http://localhost:9200";
|
||||
+11
-18
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2023 the original author or authors.
|
||||
* Copyright 2018-2021 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,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.erhlc;
|
||||
package org.springframework.data.elasticsearch.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
@@ -43,10 +43,8 @@ import org.apache.http.protocol.HttpContext;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.client.RestHighLevelClientBuilder;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
import org.springframework.data.elasticsearch.client.ClientLogger;
|
||||
import org.springframework.data.elasticsearch.support.HttpHeaders;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -60,9 +58,7 @@ import org.springframework.util.Assert;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Nic Hines
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public final class RestClients {
|
||||
|
||||
/**
|
||||
@@ -127,7 +123,8 @@ public final class RestClients {
|
||||
|
||||
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
|
||||
.getClientConfigurers()) {
|
||||
if (clientConfigurer instanceof RestClientConfigurationCallback restClientConfigurationCallback) {
|
||||
if (clientConfigurer instanceof RestClientConfigurationCallback) {
|
||||
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
|
||||
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
|
||||
}
|
||||
}
|
||||
@@ -135,7 +132,7 @@ public final class RestClients {
|
||||
return clientBuilder;
|
||||
});
|
||||
|
||||
RestHighLevelClient client = new RestHighLevelClientBuilder(builder.build()).setApiCompatibilityMode(true).build();
|
||||
RestHighLevelClient client = new RestHighLevelClient(builder);
|
||||
return () -> client;
|
||||
}
|
||||
|
||||
@@ -184,9 +181,7 @@ public final class RestClients {
|
||||
*
|
||||
* @see ClientLogger
|
||||
* @since 3.2
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
private static class HttpLoggingInterceptor implements HttpResponseInterceptor, HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
@@ -199,9 +194,9 @@ public final class RestClients {
|
||||
context.setAttribute(RestClients.LOG_ID_ATTRIBUTE, logId);
|
||||
}
|
||||
|
||||
if (request instanceof HttpEntityEnclosingRequest entityRequest
|
||||
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
if (request instanceof HttpEntityEnclosingRequest && ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
|
||||
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
|
||||
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
entity.writeTo(buffer);
|
||||
@@ -220,7 +215,7 @@ public final class RestClients {
|
||||
@Override
|
||||
public void process(HttpResponse response, HttpContext context) {
|
||||
String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE);
|
||||
ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode());
|
||||
ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +236,7 @@ public final class RestClients {
|
||||
public void process(HttpRequest request, HttpContext context) {
|
||||
HttpHeaders httpHeaders = headersSupplier.get();
|
||||
|
||||
if (httpHeaders != null && !httpHeaders.isEmpty()) {
|
||||
if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) {
|
||||
Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader);
|
||||
}
|
||||
}
|
||||
@@ -252,9 +247,7 @@ public final class RestClients {
|
||||
* the RestClient with a {@link HttpAsyncClientBuilder}
|
||||
*
|
||||
* @since 4.3
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface RestClientConfigurationCallback
|
||||
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.transport.client.PreBuiltTransportClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* TransportClientFactoryBean
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Jakub Vavrik
|
||||
* @author Piotr Betkier
|
||||
* @author Ilkang Na
|
||||
* @author Oliver Gierke
|
||||
* @author Peter-Josef Meisch
|
||||
* @deprecated as of 4.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class TransportClientFactoryBean implements FactoryBean<TransportClient>, InitializingBean, DisposableBean {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TransportClientFactoryBean.class);
|
||||
private ClusterNodes clusterNodes = ClusterNodes.of("127.0.0.1:9300");
|
||||
private String clusterName = "elasticsearch";
|
||||
private Boolean clientTransportSniff = true;
|
||||
private Boolean clientIgnoreClusterName = Boolean.FALSE;
|
||||
private String clientPingTimeout = "5s";
|
||||
private String clientNodesSamplerInterval = "5s";
|
||||
private @Nullable TransportClient client;
|
||||
private @Nullable Properties properties;
|
||||
|
||||
@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 TransportClient getObject() {
|
||||
|
||||
if (clientTransportSniff == null) {
|
||||
throw new FactoryBeanNotInitializedException();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TransportClient> getObjectType() {
|
||||
return TransportClient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
buildClient();
|
||||
}
|
||||
|
||||
protected void buildClient() {
|
||||
|
||||
client = new PreBuiltTransportClient(settings());
|
||||
|
||||
clusterNodes.stream() //
|
||||
.peek(it -> logger.info("Adding transport node : " + it.toString())) //
|
||||
.forEach(client::addTransportAddress);
|
||||
|
||||
client.connectedNodes();
|
||||
}
|
||||
|
||||
private Settings settings() {
|
||||
if (properties != null) {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
|
||||
properties.forEach((key, value) -> {
|
||||
builder.put(key.toString(), value.toString());
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
return Settings.builder().put("cluster.name", clusterName).put("client.transport.sniff", clientTransportSniff)
|
||||
.put("client.transport.ignore_cluster_name", clientIgnoreClusterName)
|
||||
.put("client.transport.ping_timeout", clientPingTimeout)
|
||||
.put("client.transport.nodes_sampler_interval", clientNodesSamplerInterval).build();
|
||||
}
|
||||
|
||||
public void setClusterNodes(String clusterNodes) {
|
||||
this.clusterNodes = ClusterNodes.of(clusterNodes);
|
||||
}
|
||||
|
||||
public void setClusterName(String clusterName) {
|
||||
this.clusterName = clusterName;
|
||||
}
|
||||
|
||||
public void setClientTransportSniff(Boolean clientTransportSniff) {
|
||||
this.clientTransportSniff = clientTransportSniff;
|
||||
}
|
||||
|
||||
public String getClientNodesSamplerInterval() {
|
||||
return clientNodesSamplerInterval;
|
||||
}
|
||||
|
||||
public void setClientNodesSamplerInterval(String clientNodesSamplerInterval) {
|
||||
this.clientNodesSamplerInterval = clientNodesSamplerInterval;
|
||||
}
|
||||
|
||||
public String getClientPingTimeout() {
|
||||
return clientPingTimeout;
|
||||
}
|
||||
|
||||
public void setClientPingTimeout(String clientPingTimeout) {
|
||||
this.clientPingTimeout = clientPingTimeout;
|
||||
}
|
||||
|
||||
public Boolean getClientIgnoreClusterName() {
|
||||
return clientIgnoreClusterName;
|
||||
}
|
||||
|
||||
public void setClientIgnoreClusterName(Boolean clientIgnoreClusterName) {
|
||||
this.clientIgnoreClusterName = clientIgnoreClusterName;
|
||||
}
|
||||
|
||||
public void setProperties(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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));
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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();
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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
@@ -1,337 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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();
|
||||
}
|
||||
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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.dao.UncategorizedDataAccessException;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 4.4
|
||||
*/
|
||||
public class CriteriaQueryException extends UncategorizedDataAccessException {
|
||||
public CriteriaQueryException(String msg) {
|
||||
super(msg, null);
|
||||
}
|
||||
}
|
||||
-386
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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().stream().map(TypeUtils::toString).toArray(),
|
||||
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
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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);
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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;
|
||||
}
|
||||
}
|
||||
-406
@@ -1,406 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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.Consumer;
|
||||
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();
|
||||
|
||||
ContentType jsonContentType = Version.VERSION == null ? ContentType.APPLICATION_JSON
|
||||
: ContentType.create("application/vnd.elasticsearch+json",
|
||||
new BasicNameValuePair("compatible-with", String.valueOf(Version.VERSION.major())));
|
||||
|
||||
Consumer<String> setHeaderIfNotPresent = header -> {
|
||||
if (transportOptionsBuilder.build().headers().stream() //
|
||||
.noneMatch((h) -> h.getKey().equalsIgnoreCase(header))) {
|
||||
// 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
|
||||
transportOptionsBuilder.addHeader(header, jsonContentType.toString());
|
||||
}
|
||||
};
|
||||
|
||||
setHeaderIfNotPresent.accept("Content-Type");
|
||||
setHeaderIfNotPresent.accept("Accept");
|
||||
|
||||
TransportOptions transportOptionsWithHeader = transportOptionsBuilder
|
||||
.addHeader(X_SPRING_DATA_ELASTICSEARCH_CLIENT, clientType).build();
|
||||
|
||||
return new RestClientTransport(restClient, new JacksonJsonpMapper(), transportOptionsWithHeader);
|
||||
}
|
||||
|
||||
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
|
||||
* @deprecated since 5.0
|
||||
*/
|
||||
@Deprecated
|
||||
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 yet, 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
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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);
|
||||
}
|
||||
}
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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();
|
||||
var errorType = response.error().type();
|
||||
var errorReason = response.error().reason() != null ? response.error().reason() : "undefined reason";
|
||||
|
||||
if (response.status() == 404 && "index_not_found_exception".equals(errorType)) {
|
||||
|
||||
// noinspection RegExpRedundantEscape
|
||||
Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]");
|
||||
String index = "";
|
||||
Matcher matcher = pattern.matcher(errorReason);
|
||||
if (matcher.matches()) {
|
||||
index = matcher.group(1);
|
||||
}
|
||||
return new NoSuchIndexException(index);
|
||||
}
|
||||
String body = JsonUtils.toJson(response, jsonpMapper);
|
||||
|
||||
if (errorType != null && errorType.contains("validation_exception")) {
|
||||
return new DataIntegrityViolationException(errorReason);
|
||||
}
|
||||
|
||||
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
@@ -1,585 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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 != null ? Version.VERSION.toString() : "0.0.0.?";
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022-2023 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2023 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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user