1
0
mirror of synced 2026-05-23 12:43:17 +00:00

Compare commits

..

19 Commits

Author SHA1 Message Date
Mark Paluch 27011d382b Release version 5.2.2 (2023.1.2).
See #2804
2024-01-12 10:51:32 +01:00
Mark Paluch 489a2605b4 Prepare 5.2.2 (2023.1.2).
See #2804
2024-01-12 10:50:44 +01:00
Peter-Josef Meisch 953f840d7c Upgrade to Elasticsearch 8.11.3.
Original Pull Request #2823
Closes #2821
2024-01-06 17:37:27 +01:00
Mark Paluch d905a4b25f Extend license header copyright years to 2024.
See #2818
2024-01-02 13:54:48 +01:00
Peter-Josef Meisch e2e6b5005b Make org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchTemplate.ClientCallback public.
Original Pull Request #2815
Closes #2814

(cherry picked from commit 260dadd4d6)
2023-12-29 13:57:56 +01:00
Peter-Josef Meisch 0d4e8ad6eb Upgrade to Elasticsearch 8.11.2.
Original Pull Request #2809
2023-12-24 14:31:20 +01:00
Mark Paluch a9bacd23b0 After release cleanups.
See #2771
2023-12-15 14:15:21 +01:00
Mark Paluch 031f7c8c78 Prepare next development iteration.
See #2771
2023-12-15 14:15:19 +01:00
Mark Paluch 76187a4934 Release version 5.2.1 (2023.1.1).
See #2771
2023-12-15 14:11:53 +01:00
Mark Paluch b693494e17 Prepare 5.2.1 (2023.1.1).
See #2771
2023-12-15 14:11:23 +01:00
Mark Paluch b324935d22 Update CI properties.
See #2771
2023-12-14 08:49:56 +01:00
Mark Paluch 920f7c029f Upgrade to Maven Wrapper 3.9.6.
See #2799
2023-12-14 08:34:15 +01:00
Peter-Josef Meisch 051777ee25 Removed junk characters from code.
(cherry picked from commit 415d5e0385)
2023-11-30 20:44:26 +01:00
Peter-Josef Meisch 0fb98eda39 Fix type of returned sort values.
Original Pull Request #2786
Closes #2777

(cherry picked from commit 3833975a1a)
2023-11-30 20:44:25 +01:00
Mark Paluch df76b43cb4 Introduce property for Jenkins user and Artifactory server details.
Closes #2781
2023-11-27 14:23:49 +01:00
Runbing b681197d0f Fixed the URL for the Spring Data Commons documentation.
I fixed broken links in the Spring Data Elasticsearch documentation for Spring Data Commons.

Closes #2776
2023-11-20 11:31:58 +01:00
Mark Paluch 8a1323c11a Update CI trigger versions.
See #2737
2023-11-17 14:53:43 +01:00
Mark Paluch 033d71ccb6 After release cleanups.
See #2737
2023-11-17 14:33:56 +01:00
Mark Paluch 0ae2a52de8 Prepare next development iteration.
See #2737
2023-11-17 14:33:54 +01:00
203 changed files with 1681 additions and 6867 deletions
+2 -1
View File
@@ -30,6 +30,7 @@ target
build/
node_modules
node
package.json
package-lock.json
.mvn/.develocity
.mvn/.gradle-enterprise
+8 -3
View File
@@ -1,8 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>io.spring.develocity.conventions</groupId>
<artifactId>develocity-conventions-maven-extension</artifactId>
<version>0.0.19</version>
<groupId>com.gradle</groupId>
<artifactId>gradle-enterprise-maven-extension</artifactId>
<version>1.19.2</version>
</extension>
<extension>
<groupId>com.gradle</groupId>
<artifactId>common-custom-user-data-maven-extension</artifactId>
<version>1.12.4</version>
</extension>
</extensions>
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<gradleEnterprise
xmlns="https://www.gradle.com/gradle-enterprise-maven" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.gradle.com/gradle-enterprise-maven https://www.gradle.com/schema/gradle-enterprise-maven.xsd">
<server>
<url>https://ge.spring.io</url>
</server>
<buildScan>
<backgroundBuildScanUpload>false</backgroundBuildScanUpload>
<captureGoalInputFiles>true</captureGoalInputFiles>
<publishIfAuthenticated>true</publishIfAuthenticated>
<obfuscation>
<ipAddresses>#{{'0.0.0.0'}}</ipAddresses>
</obfuscation>
</buildScan>
<buildCache>
<local>
<enabled>true</enabled>
</local>
<remote>
<server>
<credentials>
<username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
<password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
</credentials>
</server>
<enabled>true</enabled>
<storeEnabled>#{env['GRADLE_ENTERPRISE_CACHE_USERNAME'] != null and env['GRADLE_ENTERPRISE_CACHE_PASSWORD'] != null}</storeEnabled>
</remote>
</buildCache>
</gradleEnterprise>
+2 -2
View File
@@ -1,3 +1,3 @@
#Thu Aug 08 10:23:16 CEST 2024
#Thu Dec 14 08:34:15 CET 2023
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.8/apache-maven-3.9.8-bin.zip
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
Vendored
+23 -23
View File
@@ -9,7 +9,7 @@ pipeline {
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/3.3.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/3.2.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -33,16 +33,15 @@ pipeline {
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
}
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
}
}
}
@@ -64,15 +63,14 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
}
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh "PROFILE=none JENKINS_USER_NAME=${p['jenkins.user.name']} ci/verify.sh"
sh "JENKINS_USER_NAME=${p['jenkins.user.name']} ci/clean.sh"
}
}
}
@@ -94,22 +92,24 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DEVELOCITY_CACHE = credentials("${p['develocity.cache.credentials']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -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=${p['artifactory.url']} " +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
"-Dartifactory.build-name=spring-data-elasticsearch " +
"-Dartifactory.build-number=spring-data-elasticsearch-${BRANCH_NAME}-build-${BUILD_NUMBER} " +
"-Dmaven.test.skip=true clean deploy -U -B"
}
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
"DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR} " +
"DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW} " +
"GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY} " +
"./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root " +
"-Dartifactory.server=${p['artifactory.url']} " +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
"-Dartifactory.build-name=spring-data-elasticsearch " +
"-Dartifactory.build-number=${BUILD_NUMBER} " +
"-Dmaven.test.skip=true clean deploy -U -B"
}
}
}
+5 -2
View File
@@ -1,4 +1,6 @@
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data Elasticsearch"]
image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data Elasticsearch"]
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.
@@ -124,7 +126,8 @@ Wed love to help!
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].
* Ask a question or chat with the community on https://app.gitter.im/#/room/#spring-projects_spring-data:gitter.im[Gitter].
* 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
+5
View File
@@ -2,7 +2,12 @@
set -euo pipefail
export DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR}
export DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW}
export JENKINS_USER=${JENKINS_USER_NAME}
# The environment variable to configure access key is still GRADLE_ENTERPRISE_ACCESS_KEY
export GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY}
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
./mvnw -s settings.xml clean -Dscan=false -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
+5 -7
View File
@@ -1,10 +1,10 @@
# Java versions
java.main.tag=17.0.12_7-jdk-focal
java.next.tag=22.0.2_9-jdk-jammy
java.main.tag=17.0.9_9-jdk-focal
java.next.tag=21.0.1_12-jdk-jammy
# Docker container images - standard
docker.java.main.image=library/eclipse-temurin:${java.main.tag}
docker.java.next.image=library/eclipse-temurin:${java.next.tag}
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}
# Supported versions of MongoDB
docker.mongodb.4.4.version=4.4.25
@@ -14,7 +14,6 @@ docker.mongodb.7.0.version=7.0.2
# Supported versions of Redis
docker.redis.6.version=6.2.13
docker.redis.7.version=7.2.4
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.16
@@ -26,10 +25,9 @@ docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -
# Credentials
docker.registry=
docker.credentials=hub.docker.com-springbuildmaster
docker.proxy.registry=https://docker-hub.usw1.packages.broadcom.com
docker.proxy.credentials=usw1_packages_broadcom_com-jenkins-token
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
artifactory.url=https://repo.spring.io
artifactory.repository.snapshot=libs-snapshot-local
develocity.cache.credentials=gradle_enterprise_cache_user
develocity.access-key=gradle_enterprise_secret_access_key
jenkins.user.name=spring-builds+jenkins
+6
View File
@@ -4,8 +4,14 @@ set -euo pipefail
mkdir -p /tmp/jenkins-home/.m2/spring-data-elasticsearch
chown -R 1001:1001 .
export DEVELOCITY_CACHE_USERNAME=${DEVELOCITY_CACHE_USR}
export DEVELOCITY_CACHE_PASSWORD=${DEVELOCITY_CACHE_PSW}
export JENKINS_USER=${JENKINS_USER_NAME}
# The environment variable to configure access key is still GRADLE_ENTERPRISE_ACCESS_KEY
export GRADLE_ENTERPRISE_ACCESS_KEY=${DEVELOCITY_ACCESS_KEY}
MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home" \
./mvnw -s settings.xml \
-P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
-10
View File
@@ -1,10 +0,0 @@
{
"dependencies": {
"antora": "3.2.0-alpha.6",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.7",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.13.0",
"@springio/asciidoctor-extensions": "1.0.0-alpha.11"
}
}
+5 -12
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>5.3.3</version>
<version>5.2.2</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.3.3</version>
<version>3.2.2</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,10 +18,10 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<springdata.commons>3.3.3</springdata.commons>
<springdata.commons>3.2.2</springdata.commons>
<!-- version of the ElasticsearchClient -->
<elasticsearch-java>8.13.4</elasticsearch-java>
<elasticsearch-java>8.11.3</elasticsearch-java>
<blockhound-junit>1.0.8.RELEASE</blockhound-junit>
<hoverfly>0.14.4</hoverfly>
@@ -324,13 +324,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>${archunit}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -479,7 +472,7 @@
<build>
<plugins>
<plugin>
<groupId>org.antora</groupId>
<groupId>io.spring.maven.antora</groupId>
<artifactId>antora-maven-plugin</artifactId>
</plugin>
</plugins>
+5 -3
View File
@@ -3,7 +3,8 @@
# The purpose of this Antora playbook is to build the docs in the current branch.
antora:
extensions:
- require: '@springio/antora-extensions'
- '@antora/collector-extension'
- require: '@springio/antora-extensions/root-component-extension'
root_component_name: 'data-elasticsearch'
site:
title: Spring Data Elasticsearch
@@ -21,12 +22,13 @@ content:
start_path: src/main/antora
asciidoc:
attributes:
page-pagination: ''
hide-uri-scheme: '@'
tabs-sync-option: '@'
chomp: 'all'
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
- '@springio/asciidoctor-extensions/javadoc-extension'
sourcemap: true
urls:
latest_version_segment: ''
@@ -36,5 +38,5 @@ runtime:
format: pretty
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip
snapshot: true
-5
View File
@@ -10,8 +10,3 @@ ext:
local: true
scan:
dir: target/classes/
- run:
command: ./mvnw package -Pdistribute
local: true
scan:
dir: target/antora
+2 -3
View File
@@ -9,7 +9,7 @@
*** xref:migration-guides/migration-guide-4.4-5.0.adoc[]
*** xref:migration-guides/migration-guide-5.0-5.1.adoc[]
*** xref:migration-guides/migration-guide-5.1-5.2.adoc[]
*** xref:migration-guides/migration-guide-5.2-5.3.adoc[]
* xref:elasticsearch.adoc[]
** xref:elasticsearch/clients.adoc[]
@@ -39,5 +39,4 @@
** xref:repositories/query-keywords-reference.adoc[]
** xref:repositories/query-return-types-reference.adoc[]
* xref:attachment$api/java/index.html[Javadoc,role=link-external,window=_blank]
* https://github.com/spring-projects/spring-data-commons/wiki[Wiki,role=link-external,window=_blank]
* https://github.com/spring-projects/spring-data-commons/wiki[Wiki]
@@ -31,7 +31,7 @@ public class MyClientConfig extends ElasticsearchConfiguration {
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
====
The javadoc:org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration[]] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The `ElasticsearchConfiguration` class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The following beans can then be injected in other Spring components:
@@ -52,13 +52,13 @@ RestClient restClient; <.>
JsonpMapper jsonpMapper; <.>
----
<.> an implementation of javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[]
<.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
<.> the low level `RestClient` from the Elasticsearch libraries
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
====
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[] to interact with the Elasticsearch cluster.
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]]
@@ -86,7 +86,7 @@ public class MyClientConfig extends ReactiveElasticsearchConfiguration {
<.> for a detailed description of the builder methods see xref:elasticsearch/clients.adoc#elasticsearch.clients.configuration[Client Configuration]
====
The javadoc:org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration[] class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The `ReactiveElasticsearchConfiguration` class allows further configuration by overriding for example the `jsonpMapper()` or `transportOptions()` methods.
The following beans can then be injected in other Spring components:
@@ -108,20 +108,20 @@ JsonpMapper jsonpMapper; <.>
the following can be injected:
<.> an implementation of javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[]
<.> 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
<.> the `JsonpMapper` user by the Elasticsearch `Transport`
====
Basically one should just use the javadoc:org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations[] to interact with the Elasticsearch cluster.
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.configuration]]
== Client Configuration
Client behaviour can be changed via the javadoc:org.springframework.data.elasticsearch.client.ClientConfiguration[] that allows to set options for SSL, connect and socket timeouts, headers and other parameters.
Client behaviour can be changed via the `ClientConfiguration` that allows to set options for SSL, connect and socket timeouts, headers and other parameters.
.Client Configuration
====
@@ -150,7 +150,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
return headers;
})
.withClientConfigurer( <.>
ElasticsearchHttpClientConfigurationCallback.from(clientBuilder -> {
ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
// ...
return clientBuilder;
}))
@@ -178,7 +178,7 @@ If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.configuration.callbacks]]
=== Client configuration callbacks
The javadoc:org.springframework.data.elasticsearch.client.ClientConfiguration[] class offers the most common parameters to configure the client.
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:
@@ -192,7 +192,6 @@ This callback provides a `org.elasticsearch.client.RestClientBuilder` that can b
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchRestClientConfigurationCallback.from(restClientBuilder -> {
// configure the Elasticsearch RestClient
return restClientBuilder;
@@ -211,7 +210,6 @@ used by the `RestClient`.
[source,java]
----
ClientConfiguration.builder()
.connectedTo("localhost:9200", "localhost:9291")
.withClientConfigurer(ElasticsearchClients.ElasticsearchHttpClientConfigurationCallback.from(httpAsyncClientBuilder -> {
// configure the HttpAsyncClient
return httpAsyncClientBuilder;
@@ -1,21 +1,9 @@
[[new-features]]
= What's new
[[new-features.5-3-1]]
== New in Spring Data Elasticsearch 5.3.1
* Upgrade to Elasticsearch 8.13.4.
[[new-features.5-3-0]]
== New in Spring Data Elasticsearch 5.3
* Upgrade to Elasticsearch 8.13.2.
* Add support for highlight queries in highlighting.
* Add shard statistics to the `SearchHit` class.
* Add support for multi search template API.
* Add support for SpEL in @Query.
* Add support for field aliases in the index mapping.
* Add support for has_child and has_parent queries.
[[new-features.5-2-2]]
== New in Spring Data Elasticsearch 5.2.2
* Upgrade to Elasticsearch 8.11.3
[[new-features.5-2-0]]
== New in Spring Data Elasticsearch 5.2
@@ -52,7 +52,7 @@ public class Statement {
return routing;
}
public void setRouting(String routing) {
public void setRouting(Routing routing) {
this.routing = routing;
}
@@ -199,7 +199,7 @@ void init() {
repository.save(
Statement.builder()
.withText("+1 for the sun")
.withRouting(savedWeather.getId())
,withRouting(savedWeather.getId())
.withRelation(new JoinField<>("vote", sunnyAnswer.getId())) <5>
.build());
}
@@ -226,7 +226,6 @@ SearchHits<Statement> hasVotes() {
Query query = NativeQuery.builder()
.withQuery(co.elastic.clients.elasticsearch._types.query_dsl.Query.of(qb -> qb
.hasChild(hc -> hc
.type("answer")
.queryName("vote")
.query(matchAllQueryAsQuery())
.scoreMode(ChildScoreMode.None)
@@ -192,7 +192,8 @@ public String getProperty() {
This annotation can be set on a String property of an entity.
This property will not be written to the mapping, it will not be stored in Elasticsearch and its value will not be read from an Elasticsearch document.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity returned from that call will contain the name of the index that an entity was saved to in that property.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity
returned from that call will contain the name of the index that an entity was saved to in that property.
This is useful when the index name is dynamically set by a bean, or when writing to a write alias.
Putting some value into such a property does not set the index into which an entity is stored!
@@ -422,6 +423,7 @@ Looking at the `Configuration` from the xref:elasticsearch/object-mapping.adoc#e
@Configuration
public class Config extends ElasticsearchConfiguration {
@NonNull
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
@@ -12,10 +12,10 @@ class Book {
@Id
private String id;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String name;
@Field(type = FieldType.Text)
@Field(type = FieldType.text)
private String summary;
@Field(type = FieldType.Integer)
@@ -316,7 +316,7 @@ Repository methods can be defined to have the following return types for returni
.Declare query on the method using the `@Query` annotation.
====
The arguments passed to the method can be inserted into placeholders in the query string. The placeholders are of the form `?0`, `?1`, `?2` etc. for the first, second, third parameter and so on.
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> {
@@ -361,202 +361,3 @@ would make an https://www.elastic.co/guide/en/elasticsearch/reference/current/qu
}
----
====
[[elasticsearch.query-methods.at-query.spel]]
=== Using SpEL Expressions
.Declare query on the method using the `@Query` annotation with SpEL expression.
====
https://docs.spring.io/spring-framework/reference/core/expressions.html[SpEL expression] is also supported when defining query in `@Query`.
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#name}"
}
}
]
}
}
""")
Page<Book> findByName(String name, Pageable pageable);
}
----
If for example the function is called with the parameter _John_, it would produce the following query body:
[source,json]
----
{
"bool":{
"must":[
{
"term":{
"name": "John"
}
}
]
}
}
----
====
.accessing parameter property.
====
Supposing that we have the following class as query parameter type:
[source,java]
----
public record QueryParameter(String value) {
}
----
It's easy to access the parameter by `#` symbol, then reference the property `value` with a simple `.`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{#parameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(QueryParameter parameter, Pageable pageable);
}
----
We can pass `new QueryParameter("John")` as the parameter now, and it will produce the same query string as above.
====
.accessing bean property.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/bean-references.html[Bean property] is also supported to access. Given that there is a bean named `queryParameter` of type `QueryParameter`, we can access the bean with symbol `@` rather than `#`, and there is no need to declare a parameter of type `QueryParameter` in the query method:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"term":{
"name": "#{@queryParameter.value}"
}
}
]
}
}
""")
Page<Book> findByName(Pageable pageable);
}
----
====
.SpEL and `Collection` param.
====
`Collection` parameter is also supported and is as easy to use as normal `String`, such as the following `terms` query:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#names}
}
}
]
}
}
""")
Page<Book> findByName(Collection<String> names, Pageable pageable);
}
----
NOTE: collection values should not be quoted when declaring the elasticsearch json query.
A collection of `names` like `List.of("name1", "name2")` will produce the following terms query:
[source,json]
----
{
"bool":{
"must":[
{
"terms":{
"name": ["name1", "name2"]
}
}
]
}
}
----
====
.access property in the `Collection` param.
====
https://docs.spring.io/spring-framework/reference/core/expressions/language-ref/collection-projection.html[SpEL Collection Projection] is convenient to use when values in the `Collection` parameter is not plain `String`:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#parameters.![value]}
}
}
]
}
}
""")
Page<Book> findByName(Collection<QueryParameter> parameters, Pageable pageable);
}
----
This will extract all the `value` property values as a new `Collection` from `QueryParameter` collection, thus takes the same effect as above.
====
.alter parameter name by using `@Param`
====
When accessing the parameter by SpEL, it's also useful to alter the parameter name to another one by `@Param` annotation in Sping Data:
[source,java]
----
interface BookRepository extends ElasticsearchRepository<Book, String> {
@Query("""
{
"bool":{
"must":[
{
"terms":{
"name": #{#another.![value]}
}
}
]
}
}
""")
Page<Book> findByName(@Param("another") Collection<QueryParameter> parameters, Pageable pageable);
}
----
====
@@ -194,7 +194,7 @@ In the following code this is used to run a query for a given gender and maximum
var runtimeField = new RuntimeField("age", "long", """ <.>
Instant currentDate = Instant.ofEpochMilli(new Date().getTime());
Instant startDate = doc['birthDate'].value.toInstant();
Instant startDate = doc['birth-date'].value.toInstant();
emit (ChronoUnit.DAYS.between(startDate, currentDate) / 365);
""");
@@ -3,10 +3,10 @@
Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see xref:elasticsearch/reactive-template.adoc[]).
* javadoc:org.springframework.data.elasticsearch.core.IndexOperations[] defines actions on index level like creating or deleting an index.
* javadoc:org.springframework.data.elasticsearch.core.DocumentOperations[] defines actions to store, update and retrieve entities based on their id.
* javadoc:org.springframework.data.elasticsearch.core.SearchOperations[] define the actions to search for multiple entities using queries
* javadoc:org.springframework.data.elasticsearch.core.ElasticsearchOperations[] combines the `DocumentOperations` and `SearchOperations` interfaces.
* `IndexOperations` defines actions on index level like creating or deleting an index.
* `DocumentOperations` defines actions to store, update and retrieve entities based on their id.
* `SearchOperations` define the actions to search for multiple entities using queries
* `ElasticsearchOperations` combines the `DocumentOperations` and `SearchOperations` interfaces.
These interfaces correspond to the structuring of the https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html[Elasticsearch API].
@@ -6,8 +6,7 @@ The following table shows the Elasticsearch and Spring versions that are used by
[cols="^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework
| 2024.0 | 5.3.3 | 8.13.4 | 6.1.x
| 2023.1 (Vaughan) | 5.2.x | 8.11.1 | 6.1.x
| 2023.1 (Vaughan) | 5.2.x | 8.11.3 | 6.1.x
| 2023.0 (Ullmann) | 5.1.x | 8.7.1 | 6.0.x
| 2022.0 (Turing) | 5.0.xfootnote:oom[Out of maintenance] | 8.5.3 | 6.0.x
| 2021.2 (Raj) | 4.4.xfootnote:oom[] | 7.17.3 | 5.3.x
@@ -49,7 +49,7 @@ Also the reactive implementation that was provided up to now has been moved here
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 coming from the new Elasticsearch client libraries.
`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]]
@@ -1,18 +0,0 @@
[[elasticsearch-migration-guide-5.2-5.3]]
= Upgrading from 5.2.x to 5.3.x
This section describes breaking changes from version 5.2.x to 5.3.x and how removed features can be replaced by new introduced features.
[[elasticsearch-migration-guide-5.2-5.3.breaking-changes]]
== Breaking Changes
During the parameter replacement in `@Query` annotated repository methods previous versions wrote the String _"null"_ into the query that was sent to Elasticsearch
when the actual parameter value was `null`. As Elasticsearch does not store `null` values, this behaviour could lead to problems, for example whent the fields to be
searched contains the string `"null"`. In Version 5.3 a `null` value in a parameter will cause a `ConversionException` to be thrown. If you are using `"null"` as the
`null_value` defined in a field mapping, then pass that string into the query instead of a Java `null`.
[[elasticsearch-migration-guide-5.2-5.3.deprecations]]
== Deprecations
=== Removals
The deprecated classes `org.springframework.data.elasticsearch.ELCQueries`
and `org.springframework.data.elasticsearch.client.elc.QueryBuilders` have been removed, use `org.springframework.data.elasticsearch.client.elc.Queries` instead.
@@ -39,173 +39,41 @@ public enum DateFormat {
basic_t_time("'T'HHmmss.SSSXXX"), //
basic_t_time_no_millis("'T'HHmmssXXX"), //
basic_week_date("YYYY'W'wwe"), // week-based-year!
/**
* @since 5.3
*/
strict_basic_week_date("YYYY'W'wwe"), // week-based-year!
basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
/**
* @since 5.3
*/
strict_basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
/**
* @since 5.3
*/
strict_basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
date("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_date("uuuu-MM-dd"), //
date_hour("uuuu-MM-dd'T'HH"), //
/**
* @since 5.3
*/
strict_date_hour("uuuu-MM-dd'T'HH"), //
date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
/**
* @since 5.3
*/
strict_date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
/**
* @since 5.3
*/
strict_date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
strict_date_optional_time_nanos("uuuu-MM-dd['T'HH:mm:ss.SSSSSSXXX]"), //
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_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
/**
* @since 5.3
*/
strict_date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in its implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
/**
* @since 5.3
*/
strict_hour("HH"), //
hour_minute("HH:mm"), //
/**
* @since 5.3
*/
strict_hour_minute("HH:mm"), //
hour_minute_second("HH:mm:ss"), //
/**
* @since 5.3
*/
strict_hour_minute_second("HH:mm:ss"), //
hour_minute_second_fraction("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_fraction("HH:mm:ss.SSS"), //
hour_minute_second_millis("HH:mm:ss.SSS"), //
/**
* @since 5.3
*/
strict_hour_minute_second_millis("HH:mm:ss.SSS"), //
ordinal_date("uuuu-DDD"), //
/**
* @since 5.3
*/
strict_ordinal_date("uuuu-DDD"), //
ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
time("HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_time("HH:mm:ss.SSSXXX"), //
time_no_millis("HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_time_no_millis("HH:mm:ssXXX"), //
t_time("'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_t_time("'T'HH:mm:ss.SSSXXX"), //
t_time_no_millis("'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_t_time_no_millis("'T'HH:mm:ssXXX"), //
week_date("YYYY-'W'ww-e"), //
/**
* @since 5.3
*/
strict_week_date("YYYY-'W'ww-e"), //
week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
/**
* @since 5.3
*/
strict_week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
/**
* @since 5.3
*/
strict_week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
weekyear(""), // no TemporalAccessor available for these 3
/**
* @since 5.3
*/
strict_weekyear(""), // no TemporalAccessor available for these 3
weekyear_week(""), //
/**
* @since 5.3
*/
strict_weekyear_week(""), //
weekyear_week_day(""), //
/**
* @since 5.3
*/
strict_strict_weekyear_week_day(""), //
year("uuuu"), //
/**
* @since 5.3
*/
strict_year("uuuu"), //
year_month("uuuu-MM"), //
/**
* @since 5.3
*/
strict_year_month("uuuu-MM"), //
year_month_day("uuuu-MM-dd"), //
/**
* @since 5.3
*/
strict_year_month_day("uuuu-MM-dd"); //
year_month_day("uuuu-MM-dd"); //
private final String pattern;
@@ -21,7 +21,6 @@ import java.lang.annotation.RetentionPolicy;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.0
*/
@Documented
@@ -60,8 +59,6 @@ public @interface HighlightParameters {
int numberOfFragments() default -1;
Query highlightQuery() default @Query;
String order() default "";
int phraseLimit() default -1;
@@ -15,6 +15,9 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -22,10 +25,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was stored
* after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created or when a
* document was indexed into a write alias.
* <p>
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was
* stored after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created
* or when a document was indexed into a write alias.
*
* This can not be used to specify the index where an entity should be written to.
*
* @author Peter-Josef Meisch
@@ -74,14 +74,7 @@ public @interface Mapping {
*/
String runtimeFieldsPath() default "";
/**
* field alias definitions to be written to the index mapping
*
* @since 5.3
*/
MappingAlias[] aliases() default {};
enum Detection {
DEFAULT, TRUE, FALSE
DEFAULT, TRUE, FALSE;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2024 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.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines a field alias in the index mapping.
*
* @author Peter-Josef Meisch
* @since 5.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MappingAlias {
/**
* the name of the alias.
*/
String name();
/**
* the path of the alias.
*/
String path();
}
@@ -25,11 +25,11 @@ import org.springframework.data.util.ReactiveWrappers;
*/
public class ElasticsearchAotPredicates {
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBRARY_AVAILABLE = (
public static final Predicate<ReactiveWrappers.ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (
lib) -> ReactiveWrappers.isAvailable(lib);
public static boolean isReactorPresent() {
return IS_REACTIVE_LIBRARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
}
}
@@ -233,15 +233,6 @@ public interface ClientConfiguration {
*/
TerminalClientConfigurationBuilder usingSsl();
/**
* Connects using https if flag is true.
*
* @param flag whether to use https in the connection
* @return the {@link TerminalClientConfigurationBuilder}
* @since 5.3
*/
TerminalClientConfigurationBuilder usingSsl(boolean flag);
/**
* Connect via {@literal https} using the given {@link SSLContext}.<br />
* <strong>NOTE</strong> You need to leave out the protocol in
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
@@ -105,13 +106,6 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder usingSsl(boolean flag) {
this.useSsl = flag;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder#usingSsl(javax.net.ssl.SSLContext)
@@ -1,70 +0,0 @@
/*
* Copyright 2024 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 java.util.function.Consumer;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.lang.Nullable;
/**
* An abstract class that serves as a base for query processors. It provides a common interface and basic functionality
* for query processing.
*
* @author Aouichaoui Youssef
* @since 5.3
*/
public abstract class AbstractQueryProcessor {
/**
* Convert a spring-data-elasticsearch {@literal query} to an Elasticsearch {@literal query}.
*
* @param query spring-data-elasticsearch {@literal query}.
* @param queryConverter correct mapped field names and the values to the converted values.
* @return an Elasticsearch {@literal query}.
*/
@Nullable
static co.elastic.clients.elasticsearch._types.query_dsl.Query getEsQuery(@Nullable Query query,
@Nullable Consumer<Query> queryConverter) {
if (query == null) {
return null;
}
if (queryConverter != null) {
queryConverter.accept(query);
}
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery criteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery stringQuery) {
esQuery = Queries.wrapperQueryAsQuery(stringQuery.getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getEsQuery(nativeQuery.getSpringDataQuery(), queryConverter);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
}
@@ -16,6 +16,7 @@
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;
@@ -39,4 +40,9 @@ public class AutoCloseableElasticsearchClient extends ElasticsearchClient implem
public void close() throws Exception {
transport.close();
}
@Override
public ElasticsearchClusterClient cluster() {
return super.cluster();
}
}
@@ -39,7 +39,6 @@ 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.elasticsearch.utils.geohash.Geohash;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
@@ -51,7 +50,6 @@ import org.springframework.util.Assert;
* filter.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
class CriteriaFilterProcessor {
@@ -70,17 +68,10 @@ class CriteriaFilterProcessor {
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
if (chainedCriteria.isOr()) {
Collection<? extends Query> queriesForEntries = queriesForEntries(chainedCriteria);
if (!queriesForEntries.isEmpty()) {
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries.forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
}
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
filterQueries.add(new Query(boolQueryBuilder.build()));
} else if (chainedCriteria.isNegating()) {
Assert.notNull(criteria.getField(), "criteria must have a field");
Collection<? extends Query> negatingFilters = buildNegatingFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries());
filterQueries.addAll(negatingFilters);
@@ -124,7 +115,6 @@ class CriteriaFilterProcessor {
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");
@@ -179,17 +169,17 @@ class CriteriaFilterProcessor {
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 distance) ? extractDistanceString(distance) : (String) values[1];
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) {
if (values[0]instanceof GeoPoint loc) {
location.latlon(latlon -> latlon.lat(loc.getLat()).lon(loc.getLon()));
} else if (values[0] instanceof Point point) {
GeoPoint loc = GeoPoint.fromPoint(point);
} 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];
@@ -230,8 +220,8 @@ class CriteriaFilterProcessor {
"single-element of boundedBy filter must be type of GeoBox or Box");
GeoBox geoBBox;
if (value instanceof Box box) {
geoBBox = GeoBox.fromBox(box);
if (value instanceof Box) {
geoBBox = GeoBox.fromBox((Box) value);
} else {
geoBBox = (GeoBox) value;
}
@@ -254,7 +244,7 @@ class CriteriaFilterProcessor {
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) {
if (values[0]instanceof GeoPoint topLeft) {
GeoPoint bottomRight = (GeoPoint) values[1];
queryBuilder.boundingBox(bb -> bb //
.tlbr(tlbr -> tlbr //
@@ -276,10 +266,7 @@ class CriteriaFilterProcessor {
.tlbr(tlbr -> tlbr //
.topLeft(glb -> {
if (isGeoHash) {
// although the builder in 8.13.2 supports geohash, the server throws an error, so we convert to a
// lat,lon string here
glb.text(Geohash.toLatLon(topLeft));
// glb.geohash(gh -> gh.geohash(topLeft));
glb.geohash(gh -> gh.geohash(topLeft));
} else {
glb.text(topLeft);
}
@@ -287,8 +274,7 @@ class CriteriaFilterProcessor {
}) //
.bottomRight(glb -> {
if (isGeoHash) {
glb.text(Geohash.toLatLon(bottomRight));
// glb.geohash(gh -> gh.geohash(bottomRight));
glb.geohash(gh -> gh.geohash(bottomRight));
} else {
glb.text(bottomRight);
}
@@ -16,27 +16,21 @@
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.Queries.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
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.elasticsearch.core.search.InnerHits;
import co.elastic.clients.json.JsonData;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
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.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.HasParentQuery;
import org.springframework.data.elasticsearch.core.query.InnerHitsQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -48,7 +42,7 @@ import org.springframework.util.Assert;
* @author Ezequiel Antúnez Camacho
* @since 4.4
*/
class CriteriaQueryProcessor extends AbstractQueryProcessor {
class CriteriaQueryProcessor {
/**
* creates a query from the criteria
@@ -116,18 +110,11 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
}
}
var filterQuery = CriteriaFilterProcessor.createQuery(criteria);
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
if (filterQuery.isEmpty()) {
return null;
}
// we need something to add the filter to
mustQueries.add(Query.of(qb -> qb.matchAll(m -> m)));
return null;
}
return new Query.Builder().bool(boolQueryBuilder -> {
Query query = new Query.Builder().bool(boolQueryBuilder -> {
if (!shouldQueries.isEmpty()) {
boolQueryBuilder.should(shouldQueries);
@@ -141,10 +128,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
boolQueryBuilder.must(mustQueries);
}
filterQuery.ifPresent(boolQueryBuilder::filter);
return boolQueryBuilder;
}).build();
return query;
}
@Nullable
@@ -187,12 +174,6 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.scoreMode(ChildScoreMode.Avg));
}
if (criteria.isNegating() && criteria.isOr()) {
final Query query = queryBuilder.build();
queryBuilder = new Query.Builder();
queryBuilder.bool(mnqb -> mnqb.mustNot(query));
}
return queryBuilder.build();
}
@@ -246,7 +227,7 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
break;
case EXPRESSION:
queryBuilder.queryString(queryStringQuery(fieldName, Objects.requireNonNull(value).toString(), boost));
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
break;
case LESS:
queryBuilder //
@@ -278,7 +259,6 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
break;
case BETWEEN:
Object[] ranges = (Object[]) value;
Assert.notNull(value, "value for a between condition must not be null");
queryBuilder //
.range(rb -> {
rb.field(fieldName);
@@ -302,10 +282,10 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
.boost(boost)); //
break;
case MATCHES:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.Or, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
break;
case MATCHES_ALL:
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.And, boost));
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
break;
case IN:
@@ -354,35 +334,9 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
queryBuilder //
.regexp(rb -> rb //
.field(fieldName) //
.value(Objects.requireNonNull(value).toString()) //
.value(value.toString()) //
.boost(boost)); //
break;
case HAS_CHILD:
if (value instanceof HasChildQuery query) {
queryBuilder.hasChild(hcb -> hcb
.type(query.getType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.minChildren(query.getMinChildren())
.maxChildren(query.getMaxChildren())
.scoreMode(scoreMode(query.getScoreMode())));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_child query");
}
break;
case HAS_PARENT:
if (value instanceof HasParentQuery query) {
queryBuilder.hasParent(hpb -> hpb
.parentType(query.getParentType())
.query(getEsQuery(query.getQuery(), null))
.innerHits(getInnerHits(query.getInnerHitsQuery()))
.ignoreUnmapped(query.getIgnoreUnmapped())
.score(query.getScore()));
} else {
throw new CriteriaQueryException("value for " + fieldName + " is not a has_parent query");
}
break;
default:
throw new CriteriaQueryException("Could not build query for " + entry);
}
@@ -405,7 +359,7 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
if (item != null) {
if (!sb.isEmpty()) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('"');
@@ -437,19 +391,4 @@ class CriteriaQueryProcessor extends AbstractQueryProcessor {
return sb.toString();
}
/**
* Convert a spring-data-elasticsearch {@literal inner_hits} to an Elasticsearch {@literal inner_hits} query.
*
* @param query spring-data-elasticsearch {@literal inner_hits}.
* @return an Elasticsearch {@literal inner_hits} query.
*/
@Nullable
private static InnerHits getInnerHits(@Nullable InnerHitsQuery query) {
if (query == null) {
return null;
}
return InnerHits.of(iqb -> iqb.from(query.getFrom()).size(query.getSize()).name(query.getName()));
}
}
@@ -49,7 +49,6 @@ import org.springframework.util.Assert;
* {@link org.springframework.data.elasticsearch.core.document.Document}
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
final class DocumentAdapters {
@@ -74,7 +73,7 @@ final class DocumentAdapters {
Map<String, SearchDocumentResponse> innerHits = new LinkedHashMap<>();
hit.innerHits().forEach((name, innerHitsResult) -> {
// noinspection ReturnOfNull
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null, null,
innerHits.put(name, SearchDocumentResponseBuilder.from(innerHitsResult.hits(), null, null, null, null,
searchDocument -> null, jsonpMapper));
});
@@ -31,10 +31,6 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
@@ -122,13 +118,7 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
*/
@Bean
public JsonpMapper jsonpMapper() {
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
// into this mapper, so we can safely keep them here.
var objectMapper = (new ObjectMapper())
.configure(SerializationFeature.INDENT_OUTPUT, false)
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
return new JacksonJsonpMapper(objectMapper);
return new JacksonJsonpMapper();
}
/**
@@ -40,7 +40,6 @@ import org.springframework.data.elasticsearch.VersionConflictException;
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
* @author Peter-Josef Meisch
* @author Junghoon Ban
* @since 4.4
*/
public class ElasticsearchExceptionTranslator implements PersistenceExceptionTranslator {
@@ -60,7 +59,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
*/
public RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = translateExceptionIfPossible(runtimeException);
@@ -29,7 +29,6 @@ import co.elastic.clients.transport.Version;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -51,7 +50,14 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte
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.*;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
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.SearchTemplateQuery;
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;
@@ -66,7 +72,6 @@ import org.springframework.util.Assert;
* @author Peter-Josef Meisch
* @author Hamid Rahimi
* @author Illia Ulianov
* @author Haibo Liu
* @since 4.4
*/
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
@@ -170,11 +175,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
doBulkOperation(queries, bulkOptions, index);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
@@ -188,18 +188,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
return responseConverter.byQueryResponse(response);
}
@Override
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
clazz, index, getRefreshPolicy());
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
return responseConverter.byQueryResponse(response);
}
@Override
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
@@ -449,10 +437,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(clazz, "clazz must not be null");
int size = queries.size();
List<MultiSearchQueryParameter> multiSearchQueryParameters = new ArrayList<>(queries.size());
for (Query query : queries) {
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, getIndexCoordinatesFor(clazz)));
}
// noinspection unchecked
return multiSearch(queries, Collections.nCopies(size, clazz), Collections.nCopies(size, index))
.stream().map(searchHits -> (SearchHits<T>) searchHits)
return doMultiSearch(multiSearchQueryParameters).stream().map(searchHits -> (SearchHits<T>) searchHits)
.collect(Collectors.toList());
}
@@ -463,7 +454,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, classes.stream().map(this::getIndexCoordinatesFor).toList());
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
@@ -475,7 +473,14 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
return multiSearch(queries, classes, Collections.nCopies(queries.size(), index));
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);
}
@Override
@@ -492,50 +497,16 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Iterator<Class<?>> it = classes.iterator();
Iterator<IndexCoordinates> indexesIt = indexes.iterator();
Assert.isTrue(!queries.isEmpty(), "queries should have at least 1 query");
boolean isSearchTemplateQuery = queries.get(0) instanceof SearchTemplateQuery;
for (Query query : queries) {
Assert.isTrue((query instanceof SearchTemplateQuery) == isSearchTemplateQuery,
"SearchTemplateQuery can't be mixed with other types of query in multiple search");
Class<?> clazz = it.next();
IndexCoordinates index = indexesIt.next();
multiSearchQueryParameters.add(new MultiSearchQueryParameter(query, clazz, index));
}
return multiSearch(multiSearchQueryParameters, isSearchTemplateQuery);
}
private List<SearchHits<?>> multiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters,
boolean isSearchTemplateQuery) {
return isSearchTemplateQuery ? doMultiTemplateSearch(multiSearchQueryParameters.stream()
.map(p -> new MultiSearchTemplateQueryParameter((SearchTemplateQuery) p.query, p.clazz, p.index))
.toList())
: doMultiSearch(multiSearchQueryParameters);
}
private List<SearchHits<?>> doMultiTemplateSearch(
List<MultiSearchTemplateQueryParameter> mSearchTemplateQueryParameters) {
MsearchTemplateRequest request = requestConverter.searchMsearchTemplateRequest(mSearchTemplateQueryParameters,
routingResolver.getRouting());
MsearchTemplateResponse<EntityAsMap> response = execute(
client -> client.msearchTemplate(request, EntityAsMap.class));
List<MultiSearchResponseItem<EntityAsMap>> responseItems = response.responses();
Assert.isTrue(mSearchTemplateQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = mSearchTemplateQueryParameters.size();
List<Class<?>> classes = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = mSearchTemplateQueryParameters
.stream().map(MultiSearchTemplateQueryParameter::index).collect(Collectors.toList());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
return doMultiSearch(multiSearchQueryParameters);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> doMultiSearch(List<MultiSearchQueryParameter> multiSearchQueryParameters) {
MsearchRequest request = requestConverter.searchMsearchRequest(multiSearchQueryParameters,
@@ -547,37 +518,22 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Assert.isTrue(multiSearchQueryParameters.size() == responseItems.size(),
"number of response items does not match number of requests");
int size = multiSearchQueryParameters.size();
List<Class<?>> classes = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::clazz).collect(Collectors.toList());
List<IndexCoordinates> indices = multiSearchQueryParameters
.stream().map(MultiSearchQueryParameter::index).collect(Collectors.toList());
List<SearchHits<?>> searchHitsList = new ArrayList<>(multiSearchQueryParameters.size());
return getSearchHitsFromMsearchResponse(size, classes, indices, responseItems);
}
/**
* {@link MsearchResponse} and {@link MsearchTemplateResponse} share the same {@link MultiSearchResponseItem}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<SearchHits<?>> getSearchHitsFromMsearchResponse(int size, List<Class<?>> classes,
List<IndexCoordinates> indices, List<MultiSearchResponseItem<EntityAsMap>> responseItems) {
List<SearchHits<?>> searchHitsList = new ArrayList<>(size);
Iterator<Class<?>> clazzIter = classes.iterator();
Iterator<IndexCoordinates> indexIter = indices.iterator();
Iterator<MultiSearchQueryParameter> queryIterator = multiSearchQueryParameters.iterator();
Iterator<MultiSearchResponseItem<EntityAsMap>> responseIterator = responseItems.iterator();
while (clazzIter.hasNext() && indexIter.hasNext()) {
while (queryIterator.hasNext()) {
MultiSearchQueryParameter queryParameter = queryIterator.next();
MultiSearchResponseItem<EntityAsMap> responseItem = responseIterator.next();
if (responseItem.isResult()) {
Class clazz = clazzIter.next();
IndexCoordinates index = indexIter.next();
Class clazz = queryParameter.clazz;
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, clazz,
index);
queryParameter.index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(clazz,
index);
queryParameter.index);
SearchHits<?> searchHits = callback.doWith(
SearchDocumentResponseBuilder.from(responseItem.result(), getEntityCreator(documentCallback), jsonpMapper));
@@ -585,8 +541,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
searchHitsList.add(searchHits);
} else {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(String.format("multisearch response contains failure: %s",
responseItem.failure().error().reason()));
LOGGER
.warn(String.format("multisearch responsecontains failure: {}", responseItem.failure().error().reason()));
}
}
}
@@ -600,12 +556,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
record MultiSearchQueryParameter(Query query, Class<?> clazz, IndexCoordinates index) {
}
/**
* value class combining the information needed for a single query in a template multisearch request.
*/
record MultiSearchTemplateQueryParameter(SearchTemplateQuery query, Class<?> clazz, IndexCoordinates index) {
}
@Override
public String openPointInTime(IndexCoordinates index, Duration keepAlive, Boolean ignoreUnavailable) {
@@ -35,17 +35,14 @@ import org.springframework.util.StringUtils;
* {@link co.elastic.clients.elasticsearch.core.search.Highlight}.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
class HighlightQueryBuilder {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final RequestConverter requestConverter;
HighlightQueryBuilder(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, RequestConverter requestConverter) {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this.mappingContext = mappingContext;
this.requestConverter = requestConverter;
}
public co.elastic.clients.elasticsearch.core.search.Highlight getHighlight(Highlight highlight,
@@ -55,7 +52,7 @@ class HighlightQueryBuilder {
// 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, type);
addParameters(highlight.getParameters(), highlightBuilder);
for (HighlightField highlightField : highlight.getFields()) {
String mappedName = mapFieldName(highlightField.getName(), type);
@@ -72,7 +69,7 @@ class HighlightQueryBuilder {
* 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, @Nullable Class<?> type) {
co.elastic.clients.elasticsearch.core.search.Highlight.Builder builder) {
if (StringUtils.hasLength(parameters.getBoundaryChars())) {
builder.boundaryChars(parameters.getBoundaryChars());
@@ -106,10 +103,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -181,10 +174,6 @@ class HighlightQueryBuilder {
builder.numberOfFragments(parameters.getNumberOfFragments());
}
if (parameters.getHighlightQuery() != null) {
builder.highlightQuery(requestConverter.getQuery(parameters.getHighlightQuery(), type));
}
if (StringUtils.hasLength(parameters.getOrder())) {
builder.order(highlighterOrder(parameters.getOrder()));
}
@@ -19,6 +19,7 @@ import co.elastic.clients.json.JsonpMapper;
import jakarta.json.stream.JsonGenerator;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.logging.Log;
@@ -43,13 +44,17 @@ final class JsonUtils {
mapper.serialize(object, generator);
generator.close();
String json = "{}";
json = baos.toString(StandardCharsets.UTF_8);
try {
json = baos.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.warn("could not read json", e);
}
return json;
}
@Nullable
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query,
JsonpMapper mapper) {
public static String queryToJson(@Nullable co.elastic.clients.elasticsearch._types.query_dsl.Query query, JsonpMapper mapper) {
if (query == null) {
return null;
@@ -16,7 +16,6 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.KnnQuery;
import co.elastic.clients.elasticsearch._types.KnnSearch;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
@@ -55,7 +54,6 @@ public class NativeQuery extends BaseQuery {
private Map<String, JsonData> searchExtensions = Collections.emptyMap();
@Nullable private KnnQuery knnQuery;
@Nullable private List<KnnSearch> knnSearches = Collections.emptyList();
public NativeQuery(NativeQueryBuilder builder) {
super(builder);
@@ -73,7 +71,6 @@ public class NativeQuery extends BaseQuery {
}
this.springDataQuery = builder.getSpringDataQuery();
this.knnQuery = builder.getKnnQuery();
this.knnSearches = builder.getKnnSearches();
}
public NativeQuery(@Nullable Query query) {
@@ -132,14 +129,6 @@ public class NativeQuery extends BaseQuery {
return knnQuery;
}
/**
* @since 5.3.1
*/
@Nullable
public List<KnnSearch> getKnnSearches() {
return knnSearches;
}
@Nullable
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
return springDataQuery;
@@ -16,7 +16,6 @@
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.KnnQuery;
import co.elastic.clients.elasticsearch._types.KnnSearch;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
@@ -27,7 +26,6 @@ import co.elastic.clients.util.ObjectBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -49,12 +47,11 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
private final Map<String, Aggregation> aggregations = new LinkedHashMap<>();
@Nullable private Suggester suggester;
@Nullable private FieldCollapse fieldCollapse;
private final List<SortOptions> sortOptions = new ArrayList<>();
private final Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
private List<SortOptions> sortOptions = new ArrayList<>();
private Map<String, JsonData> searchExtensions = new LinkedHashMap<>();
@Nullable private org.springframework.data.elasticsearch.core.query.Query springDataQuery;
@Nullable private KnnQuery knnQuery;
@Nullable private List<KnnSearch> knnSearches = Collections.emptyList();
public NativeQueryBuilder() {}
@@ -95,14 +92,6 @@ public class NativeQueryBuilder extends BaseQueryBuilder<NativeQuery, NativeQuer
return knnQuery;
}
/**
* @since 5.3.1
*/
@Nullable
public List<KnnSearch> getKnnSearches() {
return knnSearches;
}
@Nullable
public org.springframework.data.elasticsearch.core.query.Query getSpringDataQuery() {
return springDataQuery;
@@ -0,0 +1,174 @@
/*
* Copyright 2022-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.LatLonGeoLocation;
import co.elastic.clients.elasticsearch._types.query_dsl.IdsQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchAllQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryStringQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.WrapperQuery;
import co.elastic.clients.util.ObjectBuilder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Utility class simplifying the creation of some more complex queries and type.
*
* @author Peter-Josef Meisch
* @since 4.4
* @deprecated since 5.1, use {@link Queries} instead.
*/
@Deprecated(forRemoval = true)
public final class QueryBuilders {
private QueryBuilders() {}
public static IdsQuery idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return IdsQuery.of(i -> i.values(ids));
}
public static Query idsQueryAsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.ids(idsQuery(ids));
return builder.apply(new Query.Builder()).build();
}
public static MatchQuery matchQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return MatchQuery.of(mb -> mb.field(fieldName).query(FieldValue.of(query)).operator(operator).boost(boost));
}
public static Query matchQueryAsQuery(String fieldName, String query, @Nullable Operator operator,
@Nullable Float boost) {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.match(matchQuery(fieldName, query, operator, boost));
return builder.apply(new Query.Builder()).build();
}
public static MatchAllQuery matchAllQuery() {
return MatchAllQuery.of(b -> b);
}
public static Query matchAllQueryAsQuery() {
Function<Query.Builder, ObjectBuilder<Query>> builder = b -> b.matchAll(matchAllQuery());
return builder.apply(new Query.Builder()).build();
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Float boost) {
return queryStringQuery(fieldName, query, null, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, Operator defaultOperator,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, null, defaultOperator, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Float boost) {
return queryStringQuery(fieldName, query, analyzeWildcard, null, boost);
}
public static QueryStringQuery queryStringQuery(String fieldName, String query, @Nullable Boolean analyzeWildcard,
@Nullable Operator defaultOperator, @Nullable Float boost) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(query, "query must not be null");
return QueryStringQuery.of(qs -> qs.fields(fieldName).query(query).analyzeWildcard(analyzeWildcard)
.defaultOperator(defaultOperator).boost(boost));
}
public static TermQuery termQuery(String fieldName, String value) {
Assert.notNull(fieldName, "fieldName must not be null");
Assert.notNull(value, "value must not be null");
return TermQuery.of(t -> t.field(fieldName).value(FieldValue.of(value)));
}
public static Query termQueryAsQuery(String fieldName, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.term(termQuery(fieldName, value));
return builder.apply(new Query.Builder()).build();
}
public static WildcardQuery wildcardQuery(String field, String value) {
Assert.notNull(field, "field must not be null");
Assert.notNull(value, "value must not be null");
return WildcardQuery.of(w -> w.field(field).wildcard(value));
}
public static Query wildcardQueryAsQuery(String field, String value) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wildcard(wildcardQuery(field, value));
return builder.apply(new Query.Builder()).build();
}
public static Query wrapperQueryAsQuery(String query) {
Function<Query.Builder, ObjectBuilder<Query>> builder = q -> q.wrapper(wrapperQuery(query));
return builder.apply(new Query.Builder()).build();
}
public static WrapperQuery wrapperQuery(String query) {
Assert.notNull(query, "query must not be null");
String encodedValue = Base64.getEncoder().encodeToString(query.getBytes(StandardCharsets.UTF_8));
return WrapperQuery.of(wq -> wq.query(encodedValue));
}
public static LatLonGeoLocation latLon(GeoPoint geoPoint) {
Assert.notNull(geoPoint, "geoPoint must not be null");
return latLon(geoPoint.getLat(), geoPoint.getLon());
}
public static LatLonGeoLocation latLon(double lat, double lon) {
return LatLonGeoLocation.of(_0 -> _0.lat(lat).lon(lon));
}
}
@@ -61,7 +61,6 @@ import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.BaseQueryBuilder;
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SearchTemplateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@@ -80,7 +79,6 @@ import org.springframework.util.StringUtils;
*
* @author Peter-Josef Meisch
* @author Illia Ulianov
* @author Junghoon Ban
* @since 4.4
*/
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
@@ -181,15 +179,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
entityType, index, getRefreshPolicy());
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
}
@Override
public <T> Mono<T> get(String id, Class<T> entityType, IndexCoordinates index) {
@@ -395,28 +384,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
Function<PitSearchAfter, Publisher<? extends ResponseBody<EntityAsMap>>> resourceClosure = psa -> {
baseQuery.setPointInTime(new Query.PointInTime(psa.getPit(), pitKeepAlive));
// only add _shard_doc if there is not a field_collapse and a sort with the same name
boolean addShardDoc = true;
if (query instanceof NativeQuery nativeQuery && nativeQuery.getFieldCollapse() != null) {
var field = nativeQuery.getFieldCollapse().field();
if (nativeQuery.getSortOptions().stream()
.anyMatch(sortOptions -> sortOptions.isField() && sortOptions.field().field().equals(field))) {
addShardDoc = false;
}
if (query.getSort() != null
&& query.getSort().stream().anyMatch(order -> order.getProperty().equals(field))) {
addShardDoc = false;
}
}
if (addShardDoc) {
baseQuery.addSort(Sort.by("_shard_doc"));
}
baseQuery.addSort(Sort.by("_shard_doc"));
SearchRequest firstSearchRequest = requestConverter.searchRequest(baseQuery, routingResolver.getRouting(),
clazz, index, false, true);
@@ -677,7 +645,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
*/
private RuntimeException translateException(Throwable throwable) {
RuntimeException runtimeException = throwable instanceof RuntimeException ex ? ex
RuntimeException runtimeException = throwable instanceof RuntimeException ? (RuntimeException) throwable
: new RuntimeException(throwable.getMessage(), throwable);
RuntimeException potentiallyTranslatedException = exceptionTranslator
.translateExceptionIfPossible(runtimeException);
@@ -44,7 +44,6 @@ import co.elastic.clients.elasticsearch.core.bulk.IndexOperation;
import co.elastic.clients.elasticsearch.core.bulk.UpdateOperation;
import co.elastic.clients.elasticsearch.core.mget.MultiGetOperation;
import co.elastic.clients.elasticsearch.core.msearch.MultisearchBody;
import co.elastic.clients.elasticsearch.core.msearch.MultisearchHeader;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Rescore;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
@@ -55,7 +54,6 @@ import co.elastic.clients.elasticsearch.indices.update_aliases.Action;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.JsonpDeserializer;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.util.ObjectBuilder;
import jakarta.json.stream.JsonParser;
import java.io.ByteArrayInputStream;
@@ -68,7 +66,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -108,11 +105,10 @@ import org.springframework.util.StringUtils;
* @author Sascha Woo
* @author cdalxndr
* @author scoobyzhang
* @author Haibo Liu
* @since 4.4
*/
@SuppressWarnings("ClassCanBeRecord")
class RequestConverter extends AbstractQueryProcessor {
class RequestConverter {
private static final Log LOGGER = LogFactory.getLog(RequestConverter.class);
@@ -157,6 +153,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -172,6 +169,7 @@ class RequestConverter extends AbstractQueryProcessor {
private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) {
// noinspection DuplicatedCode
if (parameters.getRouting() != null) {
aliasBuilder.routing(parameters.getRouting());
}
@@ -398,7 +396,10 @@ class RequestConverter extends AbstractQueryProcessor {
.order(putTemplateRequest.getOrder());
if (putTemplateRequest.getSettings() != null) {
Map<String, JsonData> settings = getTemplateParams(putTemplateRequest.getSettings().entrySet());
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> settings = putTemplateRequest.getSettings().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.settings(settings);
}
@@ -414,6 +415,7 @@ class RequestConverter extends AbstractQueryProcessor {
if (aliasActions != null) {
aliasActions.getActions().forEach(aliasAction -> {
AliasActionParameters parameters = aliasAction.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
@@ -447,6 +449,7 @@ class RequestConverter extends AbstractQueryProcessor {
aliasActions.getActions().forEach(aliasAction -> {
if (aliasAction instanceof AliasAction.Add add) {
var parameters = add.getParameters();
// noinspection DuplicatedCode
String[] parametersAliases = parameters.getAliases();
if (parametersAliases != null) {
for (String aliasName : parametersAliases) {
@@ -542,12 +545,13 @@ class RequestConverter extends AbstractQueryProcessor {
Object queryObject = query.getObject();
if (queryObject != null) {
builder
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
} else if (query.getSource() != null) {
builder
.id(query.getId())
builder //
.id(query.getId()) //
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
} else {
throw new InvalidDataAccessApiUsageException(
@@ -593,13 +597,12 @@ class RequestConverter extends AbstractQueryProcessor {
Object queryObject = query.getObject();
if (queryObject != null) {
builder
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
} else if (query.getSource() != null) {
builder
.id(query.getId())
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
} else {
throw new InvalidDataAccessApiUsageException(
"object or source is null, failed to index the document [id: " + query.getId() + ']');
@@ -635,13 +638,12 @@ class RequestConverter extends AbstractQueryProcessor {
Object queryObject = query.getObject();
if (queryObject != null) {
builder
.id(StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject))
String id = StringUtils.hasText(query.getId()) ? query.getId() : getPersistentEntityId(queryObject);
builder //
.id(id) //
.document(elasticsearchConverter.mapObject(queryObject));
} else if (query.getSource() != null) {
builder
.id(query.getId())
.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
builder.document(new DefaultStringObjectMap<>().fromJson(query.getSource()));
} else {
throw new InvalidDataAccessApiUsageException(
"object or source is null, failed to index the document [id: " + query.getId() + ']');
@@ -964,78 +966,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
public DeleteByQueryRequest documentDeleteByQueryRequest(DeleteQuery query, @Nullable String routing, Class<?> clazz,
IndexCoordinates index, @Nullable RefreshPolicy refreshPolicy) {
Assert.notNull(query, "query must not be null");
Assert.notNull(index, "index must not be null");
return DeleteByQueryRequest.of(dqb -> {
dqb.index(Arrays.asList(index.getIndexNames())) //
.query(getQuery(query.getQuery(), clazz))//
.refresh(deleteByQueryRefresh(refreshPolicy))
.requestsPerSecond(query.getRequestsPerSecond())
.maxDocs(query.getMaxDocs())
.scroll(time(query.getScroll()))
.scrollSize(query.getScrollSize());
if (query.getRouting() != null) {
dqb.routing(query.getRouting());
} else if (StringUtils.hasText(routing)) {
dqb.routing(routing);
}
if (query.getQ() != null) {
dqb.q(query.getQ())
.analyzer(query.getAnalyzer())
.analyzeWildcard(query.getAnalyzeWildcard())
.defaultOperator(operator(query.getDefaultOperator()))
.df(query.getDf())
.lenient(query.getLenient());
}
if (query.getExpandWildcards() != null && !query.getExpandWildcards().isEmpty()) {
dqb.expandWildcards(expandWildcards(query.getExpandWildcards()));
}
if (query.getStats() != null && !query.getStats().isEmpty()) {
dqb.stats(query.getStats());
}
if (query.getSlices() != null) {
dqb.slices(sb -> sb.value(query.getSlices()));
}
if (query.getSort() != null) {
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntity(clazz);
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
if (!sortOptions.isEmpty()) {
dqb.sort(
sortOptions.stream()
.map(sortOption -> {
String order = "asc";
var sortField = sortOption.field();
if (sortField.order() != null) {
order = sortField.order().jsonValue();
}
return sortField.field() + ":" + order;
})
.collect(Collectors.toList()));
}
}
dqb.allowNoIndices(query.getAllowNoIndices())
.conflicts(conflicts(query.getConflicts()))
.ignoreUnavailable(query.getIgnoreUnavailable())
.preference(query.getPreference())
.requestCache(query.getRequestCache())
.searchType(searchType(query.getSearchType()))
.searchTimeout(time(query.getSearchTimeout()))
.terminateAfter(query.getTerminateAfter())
.timeout(time(query.getTimeout()))
.version(query.getVersion());
return dqb;
});
}
public UpdateRequest<Document, ?> documentUpdateRequest(UpdateQuery query, IndexCoordinates index,
@Nullable RefreshPolicy refreshPolicy, @Nullable String routing) {
@@ -1210,40 +1140,11 @@ class RequestConverter extends AbstractQueryProcessor {
builder.routing(routing);
}
addPostFilter(query, builder);
addFilter(query, builder);
return builder.build();
}
public MsearchTemplateRequest searchMsearchTemplateRequest(
List<ElasticsearchTemplate.MultiSearchTemplateQueryParameter> multiSearchTemplateQueryParameters,
@Nullable String routing) {
// basically the same stuff as in template search
return MsearchTemplateRequest.of(mtrb -> {
multiSearchTemplateQueryParameters.forEach(param -> {
var query = param.query();
mtrb.searchTemplates(stb -> stb
.header(msearchHeaderBuilder(query, param.index(), routing))
.body(bb -> {
bb //
.explain(query.getExplain()) //
.id(query.getId()) //
.source(query.getSource()) //
;
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
bb.params(params);
}
return bb;
}));
});
return mtrb;
});
}
public MsearchRequest searchMsearchRequest(
List<ElasticsearchTemplate.MultiSearchQueryParameter> multiSearchQueryParameters, @Nullable String routing) {
@@ -1255,7 +1156,28 @@ class RequestConverter extends AbstractQueryProcessor {
var query = param.query();
mrb.searches(sb -> sb //
.header(msearchHeaderBuilder(query, param.index(), routing)) //
.header(h -> {
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
h //
.index(Arrays.asList(param.index().getIndexNames())) //
.searchType(searchType) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
}) //
.body(bb -> {
bb //
.query(getQuery(query, param.clazz()))//
@@ -1361,35 +1283,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
/**
* {@link MsearchRequest} and {@link MsearchTemplateRequest} share the same {@link MultisearchHeader}
*/
private Function<MultisearchHeader.Builder, ObjectBuilder<MultisearchHeader>> msearchHeaderBuilder(Query query,
IndexCoordinates index, @Nullable String routing) {
return h -> {
var searchType = (query instanceof NativeQuery nativeQuery && nativeQuery.getKnnQuery() != null) ? null
: searchType(query.getSearchType());
h //
.index(Arrays.asList(index.getIndexNames())) //
.searchType(searchType) //
.requestCache(query.getRequestCache()) //
;
if (StringUtils.hasText(query.getRoute())) {
h.routing(query.getRoute());
} else if (StringUtils.hasText(routing)) {
h.routing(routing);
}
if (query.getPreference() != null) {
h.preference(query.getPreference());
}
return h;
};
}
private <T> void prepareSearchRequest(Query query, @Nullable String routing, @Nullable Class<T> clazz,
IndexCoordinates indexCoordinates, SearchRequest.Builder builder, boolean forCount, boolean forBatchedSearch) {
@@ -1477,8 +1370,8 @@ class RequestConverter extends AbstractQueryProcessor {
if (query instanceof NativeQuery nativeQuery) {
prepareNativeSearch(nativeQuery, builder);
}
// query.getSort() must be checked after prepareNativeSearch as this already might have a sort set
// that must have higher priority
// query.getSort() must be checked after prepareNativeSearch as this already might hav a sort set that must have
// higher priority
if (query.getSort() != null) {
List<SortOptions> sortOptions = getSortOptions(query.getSort(), persistentEntity);
@@ -1500,15 +1393,7 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!isEmpty(query.getSearchAfter())) {
var fieldValues = query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList();
// when there is a field collapse on a native query, and we have a search_after, then the search_after
// must only have one entry
if (query instanceof NativeQuery nativeQuery && nativeQuery.getFieldCollapse() != null) {
builder.searchAfter(fieldValues.get(0));
} else {
builder.searchAfter(fieldValues);
}
builder.searchAfter(query.getSearchAfter().stream().map(TypeUtils::toFieldValue).toList());
}
query.getRescorerQueries().forEach(rescorerQuery -> builder.rescore(getRescore(rescorerQuery)));
@@ -1609,7 +1494,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, SearchRequest.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1619,7 +1504,7 @@ class RequestConverter extends AbstractQueryProcessor {
private void addHighlight(Query query, MultisearchBody.Builder builder) {
Highlight highlight = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext(), this)
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlight(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
@@ -1727,18 +1612,7 @@ class RequestConverter extends AbstractQueryProcessor {
;
if (query.getKnnQuery() != null) {
var kq = query.getKnnQuery();
builder.knn(ksb -> ksb
.field(kq.field())
.queryVector(kq.queryVector())
.numCandidates(kq.numCandidates())
.filter(kq.filter())
.similarity(kq.similarity()));
}
if (!isEmpty(query.getKnnSearches())) {
builder.knn(query.getKnnSearches());
builder.knn(query.getKnnQuery());
}
if (!isEmpty(query.getAggregations())) {
@@ -1759,18 +1633,7 @@ class RequestConverter extends AbstractQueryProcessor {
.sort(query.getSortOptions());
if (query.getKnnQuery() != null) {
var kq = query.getKnnQuery();
builder.knn(ksb -> ksb
.field(kq.field())
.queryVector(kq.queryVector())
.numCandidates(kq.numCandidates())
.filter(kq.filter())
.similarity(kq.similarity()));
}
if (!isEmpty(query.getKnnSearches())) {
builder.knn(query.getKnnSearches());
builder.knn(query.getKnnQuery());
}
if (!isEmpty(query.getAggregations())) {
@@ -1783,23 +1646,46 @@ class RequestConverter extends AbstractQueryProcessor {
}
@Nullable
co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
private co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query query,
@Nullable Class<?> clazz) {
return getEsQuery(query, (q) -> elasticsearchConverter.updateQuery(q, clazz));
if (query == null) {
return null;
}
elasticsearchConverter.updateQuery(query, clazz);
co.elastic.clients.elasticsearch._types.query_dsl.Query esQuery = null;
if (query instanceof CriteriaQuery) {
esQuery = CriteriaQueryProcessor.createQuery(((CriteriaQuery) query).getCriteria());
} else if (query instanceof StringQuery) {
esQuery = Queries.wrapperQueryAsQuery(((StringQuery) query).getSource());
} else if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getQuery() != null) {
esQuery = nativeQuery.getQuery();
} else if (nativeQuery.getSpringDataQuery() != null) {
esQuery = getQuery(nativeQuery.getSpringDataQuery(), clazz);
}
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
return esQuery;
}
@SuppressWarnings("StatementWithEmptyBody")
private void addPostFilter(Query query, SearchRequest.Builder builder) {
private void addFilter(Query query, SearchRequest.Builder builder) {
// we only need to handle NativeQuery here. filter from a CriteriaQuery are added into the query and not as post
// filter anymore, StringQuery do not have post filters
if (query instanceof NativeQuery nativeQuery) {
if (nativeQuery.getFilter() != null) {
builder.postFilter(nativeQuery.getFilter());
} else if (nativeQuery.getSpringDataQuery() != null) {
addPostFilter(nativeQuery.getSpringDataQuery(), builder);
}
if (query instanceof CriteriaQuery) {
CriteriaFilterProcessor.createQuery(((CriteriaQuery) query).getCriteria()).ifPresent(builder::postFilter);
} else // noinspection StatementWithEmptyBody
if (query instanceof StringQuery) {
// no filter for StringQuery
} else if (query instanceof NativeQuery) {
builder.postFilter(((NativeQuery) query).getFilter());
} else {
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
}
}
@@ -1883,8 +1769,7 @@ class RequestConverter extends AbstractQueryProcessor {
.id(query.getId()) //
.index(Arrays.asList(index.getIndexNames())) //
.preference(query.getPreference()) //
.searchType(searchType(query.getSearchType())) //
.source(query.getSource()) //
.searchType(searchType(query.getSearchType())).source(query.getSource()) //
;
if (query.getRoute() != null) {
@@ -1903,7 +1788,10 @@ class RequestConverter extends AbstractQueryProcessor {
}
if (!CollectionUtils.isEmpty(query.getParams())) {
Map<String, JsonData> params = getTemplateParams(query.getParams().entrySet());
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
Map<String, JsonData> params = query.getParams().entrySet().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
builder.params(params);
}
@@ -1911,14 +1799,6 @@ class RequestConverter extends AbstractQueryProcessor {
});
}
@NotNull
private Map<String, JsonData> getTemplateParams(Set<Map.Entry<String, Object>> query) {
Function<Map.Entry<String, Object>, String> keyMapper = Map.Entry::getKey;
Function<Map.Entry<String, Object>, JsonData> valueMapper = entry -> JsonData.of(entry.getValue(), jsonpMapper);
return query.stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
}
// endregion
public PutScriptRequest scriptPut(Script script) {
@@ -15,8 +15,9 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.toJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.removePrefixFromJson;
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.typeMapping;
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
import co.elastic.clients.elasticsearch._types.ErrorCause;
@@ -35,12 +36,7 @@ import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplate
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
import co.elastic.clients.json.JsonpMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -51,11 +47,7 @@ import org.springframework.data.elasticsearch.core.IndexInformation;
import org.springframework.data.elasticsearch.core.MultiGetItem;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.index.TemplateResponse;
import org.springframework.data.elasticsearch.core.index.TemplateResponseData;
import org.springframework.data.elasticsearch.core.index.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.StringQuery;
@@ -129,7 +121,8 @@ class ResponseConverter {
.build();
}
private TemplateResponseData clusterGetComponentTemplateData(ComponentTemplateSummary componentTemplateSummary) {
private TemplateResponseData clusterGetComponentTemplateData(
ComponentTemplateSummary componentTemplateSummary) {
var mapping = typeMapping(componentTemplateSummary.mappings());
var settings = new Settings();
@@ -190,7 +183,7 @@ class ResponseConverter {
Map<String, IndexMappingRecord> mappings = getMappingResponse.result();
if (mappings == null || mappings.isEmpty()) {
if (mappings == null || mappings.size() == 0) {
return Document.create();
}
@@ -333,7 +326,7 @@ class ResponseConverter {
}
private TemplateResponseData indexGetComponentTemplateData(IndexTemplateSummary indexTemplateSummary,
List<String> composedOf) {
List<String> composedOf) {
var mapping = typeMapping(indexTemplateSummary.mappings());
Function<IndexSettings, Settings> indexSettingsToSettings = indexSettings -> {
@@ -548,7 +541,7 @@ class ResponseConverter {
}
@Nullable
static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
private static ElasticsearchErrorCause toErrorCause(@Nullable ErrorCause errorCause) {
if (errorCause != null) {
return new ElasticsearchErrorCause( //
@@ -15,8 +15,6 @@
*/
package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.ShardFailure;
import co.elastic.clients.elasticsearch._types.ShardStatistics;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.SearchTemplateResponse;
@@ -38,7 +36,6 @@ import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
@@ -55,7 +52,6 @@ import org.springframework.util.CollectionUtils;
* Factory class to create {@link SearchDocumentResponse} instances.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.4
*/
class SearchDocumentResponseBuilder {
@@ -82,9 +78,8 @@ class SearchDocumentResponseBuilder {
Map<String, Aggregate> aggregations = responseBody.aggregations();
Map<String, List<Suggestion<EntityAsMap>>> suggest = responseBody.suggest();
var pointInTimeId = responseBody.pitId();
var shards = responseBody.shards();
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -103,14 +98,13 @@ class SearchDocumentResponseBuilder {
Assert.notNull(entityCreator, "entityCreator must not be null");
Assert.notNull(jsonpMapper, "jsonpMapper must not be null");
var shards = response.shards();
var hitsMetadata = response.hits();
var scrollId = response.scrollId();
var aggregations = response.aggregations();
var suggest = response.suggest();
var pointInTimeId = response.pitId();
return from(hitsMetadata, shards, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
return from(hitsMetadata, scrollId, pointInTimeId, aggregations, suggest, entityCreator, jsonpMapper);
}
/**
@@ -126,8 +120,8 @@ class SearchDocumentResponseBuilder {
* @param jsonpMapper to map JsonData objects
* @return the {@link SearchDocumentResponse}
*/
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable ShardStatistics shards,
@Nullable String scrollId, @Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
public static <T> SearchDocumentResponse from(HitsMetadata<?> hitsMetadata, @Nullable String scrollId,
@Nullable String pointInTimeId, @Nullable Map<String, Aggregate> aggregations,
Map<String, List<Suggestion<EntityAsMap>>> suggestES, SearchDocumentResponse.EntityCreator<T> entityCreator,
JsonpMapper jsonpMapper) {
@@ -161,18 +155,8 @@ class SearchDocumentResponseBuilder {
Suggest suggest = suggestFrom(suggestES, entityCreator);
SearchShardStatistics shardStatistics = shards != null ? shardsFrom(shards) : null;
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchDocuments,
aggregationsContainer, suggest, shardStatistics);
}
private static SearchShardStatistics shardsFrom(ShardStatistics shards) {
List<ShardFailure> failures = shards.failures();
List<SearchShardStatistics.Failure> searchFailures = failures.stream().map(f -> SearchShardStatistics.Failure
.of(f.index(), f.node(), f.status(), f.shard(), null, ResponseConverter.toErrorCause(f.reason()))).toList();
return SearchShardStatistics.of(shards.failed(), shards.successful(), shards.total(), shards.skipped(),
searchFailures);
aggregationsContainer, suggest);
}
@Nullable
@@ -234,8 +218,9 @@ class SearchDocumentResponseBuilder {
var phraseSuggest = suggestionES.phrase();
var phraseSuggestOptions = phraseSuggest.options();
List<PhraseSuggestion.Entry.Option> options = new ArrayList<>();
phraseSuggestOptions.forEach(optionES -> options.add(new PhraseSuggestion.Entry.Option(optionES.text(),
optionES.highlighted(), optionES.score(), optionES.collateMatch())));
phraseSuggestOptions.forEach(optionES -> options
.add(new PhraseSuggestion.Entry.Option(optionES.text(), optionES.highlighted(), optionES.score(),
optionES.collateMatch())));
entries.add(new PhraseSuggestion.Entry(phraseSuggest.text(), phraseSuggest.offset(), phraseSuggest.length(),
options, null));
});
@@ -18,8 +18,6 @@ package org.springframework.data.elasticsearch.client.elc;
import co.elastic.clients.elasticsearch._types.*;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import co.elastic.clients.elasticsearch.core.search.BoundaryScanner;
import co.elastic.clients.elasticsearch.core.search.HighlighterEncoder;
import co.elastic.clients.elasticsearch.core.search.HighlighterFragmenter;
@@ -42,15 +40,12 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder;
import org.springframework.data.elasticsearch.core.query.HasChildQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndicesOptions;
import org.springframework.data.elasticsearch.core.query.Order;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RescorerQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -505,48 +500,4 @@ final class TypeUtils {
});
return mappedParams;
}
/**
* Convert a spring-data-elasticsearch operator to an Elasticsearch operator.
*
* @param operator spring-data-elasticsearch operator.
* @return an Elasticsearch Operator.
* @since 5.3
*/
@Nullable
static Operator operator(@Nullable OperatorType operator) {
return operator != null ? Operator.valueOf(operator.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal conflicts} to an Elasticsearch {@literal conflicts}.
*
* @param conflicts spring-data-elasticsearch {@literal conflicts}.
* @return an Elasticsearch {@literal conflicts}.
* @since 5.3
*/
@Nullable
static Conflicts conflicts(@Nullable ConflictsType conflicts) {
return conflicts != null ? Conflicts.valueOf(conflicts.name()) : null;
}
/**
* Convert a spring-data-elasticsearch {@literal scoreMode} to an Elasticsearch {@literal scoreMode}.
*
* @param scoreMode spring-data-elasticsearch {@literal scoreMode}.
* @return an Elasticsearch {@literal scoreMode}.
*/
static ChildScoreMode scoreMode(@Nullable HasChildQuery.ScoreMode scoreMode) {
if (scoreMode == null) {
return ChildScoreMode.None;
}
return switch (scoreMode) {
case Avg -> ChildScoreMode.Avg;
case Max -> ChildScoreMode.Max;
case Min -> ChildScoreMode.Min;
case Sum -> ChildScoreMode.Sum;
default -> ChildScoreMode.None;
};
}
}
@@ -57,6 +57,7 @@ import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -299,7 +300,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
@Override
@Deprecated
public ByQueryResponse delete(Query query, Class<?> clazz) {
return delete(query, clazz, getIndexCoordinatesFor(clazz));
}
@@ -779,7 +779,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
protected interface SearchDocumentResponseCallback<T> {
T doWith(SearchDocumentResponse response);
@NonNull
T doWith(@NonNull SearchDocumentResponse response);
}
protected class ReadSearchDocumentResponseCallback<T> implements SearchDocumentResponseCallback<SearchHits<T>> {
@@ -794,6 +795,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -814,6 +816,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.type = type;
}
@NonNull
@Override
public SearchScrollHits<T> doWith(SearchDocumentResponse response) {
List<T> entities = response.getSearchDocuments().stream().map(delegate::doWith).collect(Collectors.toList());
@@ -46,7 +46,6 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
@@ -56,6 +55,7 @@ import org.springframework.data.elasticsearch.core.script.Script;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -409,15 +409,9 @@ abstract public class AbstractReactiveElasticsearchTemplate
abstract protected Mono<String> doDeleteById(String id, @Nullable String routing, IndexCoordinates index);
@Override
@Deprecated
public Mono<ByQueryResponse> delete(Query query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType) {
return delete(query, entityType, getIndexCoordinatesFor(entityType));
}
// endregion
// region SearchDocument
@@ -581,6 +575,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
* @param document the document to convert
* @return a Mono of the entity
*/
@NonNull
Mono<T> toEntity(@Nullable Document document);
}
@@ -598,6 +593,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
this.index = index;
}
@NonNull
public Mono<T> toEntity(@Nullable Document document) {
if (document == null) {
return Mono.empty();
@@ -21,7 +21,6 @@ import java.util.List;
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.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@@ -280,22 +279,9 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 4.1
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
@Deprecated
ByQueryResponse delete(Query query, Class<?> clazz);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz);
/**
* Delete all records matching the query.
*
@@ -304,23 +290,9 @@ public interface DocumentOperations {
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
@Deprecated
ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index);
/**
* Delete all records matching the query.
*
* @param query query defining the objects
* @param clazz The entity class must be annotated with
* {@link org.springframework.data.elasticsearch.annotations.Document}
* @param index the index from which to delete
* @return response with detailed information
* @since 5.3
*/
ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index);
/**
* Partially update a document by the given entity.
*
@@ -25,7 +25,6 @@ import java.util.List;
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.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
@@ -332,21 +331,9 @@ public interface ReactiveDocumentOperations {
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class)}
*/
@Deprecated
Mono<ByQueryResponse> delete(Query query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
@@ -354,22 +341,9 @@ public interface ReactiveDocumentOperations {
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @deprecated since 5.3.0, use {@link #delete(DeleteQuery, Class, IndexCoordinates)}
*/
@Deprecated
Mono<ByQueryResponse> delete(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Delete the documents matching the given {@link Query} extracting index from entity metadata.
*
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
* @return a {@link Mono} emitting the number of the removed documents.
* @since 5.3
*/
Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index);
/**
* Partial update of the document.
*
@@ -22,5 +22,5 @@ package org.springframework.data.elasticsearch.core;
* @since 4.2
*/
public enum RefreshPolicy {
NONE, IMMEDIATE, WAIT_UNTIL
NONE, IMMEDIATE, WAIT_UNTIL;
}
@@ -46,7 +46,6 @@ import org.springframework.util.Assert;
* @author Matt Gilene
* @author Sascha Woo
* @author Jakob Hoeper
* @author Haibo Liu
* @since 4.0
*/
public class SearchHitMapping<T> {
@@ -85,7 +84,6 @@ public class SearchHitMapping<T> {
"Count of documents must match the count of entities");
long totalHits = searchDocumentResponse.getTotalHits();
SearchShardStatistics shardStatistics = searchDocumentResponse.getSearchShardStatistics();
float maxScore = searchDocumentResponse.getMaxScore();
String scrollId = searchDocumentResponse.getScrollId();
String pointInTimeId = searchDocumentResponse.getPointInTimeId();
@@ -105,7 +103,7 @@ public class SearchHitMapping<T> {
mapHitsInCompletionSuggestion(suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, pointInTimeId, searchHits,
aggregations, suggest, shardStatistics);
aggregations, suggest);
}
@SuppressWarnings("unchecked")
@@ -167,7 +165,7 @@ public class SearchHitMapping<T> {
Map<String, SearchHits<?>> innerHits = new LinkedHashMap<>();
Map<String, SearchDocumentResponse> documentInnerHits = searchDocument.getInnerHits();
if (documentInnerHits != null && !documentInnerHits.isEmpty()) {
if (documentInnerHits != null && documentInnerHits.size() > 0) {
SearchHitMapping<SearchDocument> searchDocumentSearchHitMapping = SearchHitMapping
.mappingFor(SearchDocument.class, converter);
@@ -235,15 +233,14 @@ public class SearchHitMapping<T> {
scrollId = searchHitsImpl.getScrollId();
}
return new SearchHitsImpl<>(searchHits.getTotalHits(),
searchHits.getTotalHitsRelation(),
searchHits.getMaxScore(),
scrollId,
searchHits.getPointInTimeId(),
convertedSearchHits,
searchHits.getAggregations(),
searchHits.getSuggest(),
searchHits.getSearchShardStatistics());
return new SearchHitsImpl<>(searchHits.getTotalHits(), //
searchHits.getTotalHitsRelation(), //
searchHits.getMaxScore(), //
scrollId, //
searchHits.getPointInTimeId(), //
convertedSearchHits, //
searchHits.getAggregations(), //
searchHits.getSuggest());
}
} catch (Exception e) {
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
@@ -287,8 +284,8 @@ public class SearchHitMapping<T> {
}
private static class ElasticsearchPersistentEntityWithNestedMetaData {
@Nullable private final ElasticsearchPersistentEntity<?> entity;
private final NestedMetaData nestedMetaData;
@Nullable private ElasticsearchPersistentEntity<?> entity;
private NestedMetaData nestedMetaData;
public ElasticsearchPersistentEntityWithNestedMetaData(@Nullable ElasticsearchPersistentEntity<?> entity,
NestedMetaData nestedMetaData) {
@@ -23,8 +23,8 @@ import java.util.stream.Stream;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.CloseableIterator;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
/**
@@ -27,7 +27,6 @@ import org.springframework.lang.Nullable;
*
* @param <T> the result data class.
* @author Sascha Woo
* @author Haibo Liu
* @since 4.0
*/
public interface SearchHits<T> extends Streamable<SearchHit<T>> {
@@ -109,10 +108,4 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
*/
@Nullable
String getPointInTimeId();
/**
* @return shard statistics for the search hit.
*/
@Nullable
SearchShardStatistics getSearchShardStatistics();
}
@@ -29,7 +29,6 @@ import org.springframework.util.Assert;
* @param <T> the result data class.
* @author Peter-Josef Meisch
* @author Sascha Woo
* @author Haibo Liu
* @since 4.0
*/
public class SearchHitsImpl<T> implements SearchScrollHits<T> {
@@ -42,8 +41,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable private final String pointInTimeId;
@Nullable private final SearchShardStatistics searchShardStatistics;
@Nullable private String pointInTimeId;
/**
* @param totalHits the number of total hits for the search
@@ -55,8 +53,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
*/
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
@Nullable String pointInTimeId, List<? extends SearchHit<T>> searchHits,
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest,
@Nullable SearchShardStatistics searchShardStatistics) {
@Nullable AggregationsContainer<?> aggregations, @Nullable Suggest suggest) {
Assert.notNull(searchHits, "searchHits must not be null");
@@ -69,7 +66,6 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
this.aggregations = aggregations;
this.suggest = suggest;
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
this.searchShardStatistics = searchShardStatistics;
}
// region getter
@@ -122,11 +118,6 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
return pointInTimeId;
}
@Override
public SearchShardStatistics getSearchShardStatistics() {
return searchShardStatistics;
}
@Override
public String toString() {
return "SearchHits{" + //
@@ -137,7 +128,6 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
", pointInTimeId='" + pointInTimeId + '\'' + //
", searchHits={" + searchHits.size() + " elements}" + //
", aggregations=" + aggregations + //
", shardStatistics=" + searchShardStatistics + //
'}';
}
}
@@ -1,130 +0,0 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import java.util.List;
import org.springframework.data.elasticsearch.ElasticsearchErrorCause;
import org.springframework.lang.Nullable;
/**
* @author Haibo Liu
* @since 5.3
*/
public class SearchShardStatistics {
private final Number failed;
private final Number successful;
private final Number total;
@Nullable private final Number skipped;
private final List<Failure> failures;
private SearchShardStatistics(Number failed, Number successful, Number total, @Nullable Number skipped,
List<Failure> failures) {
this.failed = failed;
this.successful = successful;
this.total = total;
this.skipped = skipped;
this.failures = failures;
}
public static SearchShardStatistics of(Number failed, Number successful, Number total, @Nullable Number skipped,
List<Failure> failures) {
return new SearchShardStatistics(failed, successful, total, skipped, failures);
}
public Number getFailed() {
return failed;
}
public Number getSuccessful() {
return successful;
}
public Number getTotal() {
return total;
}
@Nullable
public Number getSkipped() {
return skipped;
}
public boolean isFailed() {
return failed.intValue() > 0;
}
public List<Failure> getFailures() {
return failures;
}
public static class Failure {
@Nullable private final String index;
@Nullable private final String node;
@Nullable private final String status;
private final int shard;
@Nullable private final Exception exception;
@Nullable private final ElasticsearchErrorCause elasticsearchErrorCause;
private Failure(@Nullable String index, @Nullable String node, @Nullable String status, int shard,
@Nullable Exception exception, @Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
this.index = index;
this.node = node;
this.status = status;
this.shard = shard;
this.exception = exception;
this.elasticsearchErrorCause = elasticsearchErrorCause;
}
public static SearchShardStatistics.Failure of(@Nullable String index, @Nullable String node,
@Nullable String status, int shard, @Nullable Exception exception,
@Nullable ElasticsearchErrorCause elasticsearchErrorCause) {
return new SearchShardStatistics.Failure(index, node, status, shard, exception, elasticsearchErrorCause);
}
@Nullable
public String getIndex() {
return index;
}
@Nullable
public String getNode() {
return node;
}
@Nullable
public String getStatus() {
return status;
}
@Nullable
public Exception getException() {
return exception;
}
public int getShard() {
return shard;
}
@Nullable
public ElasticsearchErrorCause getElasticsearchErrorCause() {
return elasticsearchErrorCause;
}
}
}
@@ -61,10 +61,10 @@ abstract class StreamQueries {
return new SearchHitsIterator<>() {
private final AtomicInteger currentCount = new AtomicInteger();
private volatile AtomicInteger currentCount = new AtomicInteger();
private volatile Iterator<SearchHit<T>> currentScrollHits = searchHits.iterator();
private volatile boolean continueScroll = currentScrollHits.hasNext();
private final ScrollState scrollState = new ScrollState(searchHits.getScrollId());
private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId());
private volatile boolean isClosed = false;
@Override
@@ -110,6 +110,6 @@ public interface ElasticsearchConverter
* @return a String wihere the property names are replaced with field names
* @since 5.2
*/
String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
public String updateFieldNames(String propertyPath, ElasticsearchPersistentEntity<?> persistentEntity);
// endregion
}
@@ -129,7 +129,7 @@ public class ElasticsearchCustomConversions extends CustomConversions {
@WritingConverter
enum ByteArrayToBase64Converter implements Converter<byte[], String> {
INSTANCE;
INSTANCE,;
@Override
public String convert(byte[] source) {
@@ -344,7 +344,7 @@ public class GeoConverters {
String type = GeoConverters.getGeoJsonType(source);
Assert.isTrue(type.equalsIgnoreCase(GeoJsonPolygon.TYPE), "does not contain a type 'Polygon'");
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
Assert.isTrue(!lines.isEmpty(), "no linestrings defined in polygon");
Assert.isTrue(lines.size() > 0, "no linestrings defined in polygon");
GeoJsonPolygon geoJsonPolygon = GeoJsonPolygon.of(lines.get(0));
for (int i = 1; i < lines.size(); i++) {
geoJsonPolygon = geoJsonPolygon.withInnerRing(lines.get(i));
@@ -1,40 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.convert;
import org.springframework.lang.Nullable;
/**
* @since 5.3
* @author Peter-Josef Meisch
*/
public class MappingConversionException extends RuntimeException {
private final String documentId;
public MappingConversionException(@Nullable String documentId, Throwable cause) {
super(cause);
this.documentId = documentId != null ? documentId : "\"null\"";
}
public String getDocumentId() {
return documentId;
}
@Override
public String getMessage() {
return "Conversion exception when converting document id " + documentId;
}
}
@@ -34,9 +34,6 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
@@ -61,7 +58,6 @@ import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.*;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -84,11 +80,10 @@ import org.springframework.util.ObjectUtils;
* @author Marc Vanbrabant
* @author Anton Naydenov
* @author vdisk
* @author Junghoon Ban
* @since 3.2
*/
public class MappingElasticsearchConverter
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean, EnvironmentCapable {
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions.";
private static final String INVALID_TYPE_TO_READ = "Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!";
@@ -98,14 +93,7 @@ public class MappingElasticsearchConverter
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
protected @Nullable Environment environment;
private final SpELContext spELContext = new SpELContext(new MapAccessor());
private final SpelExpressionParser expressionParser = new SpelExpressionParser();
private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory = new CachingValueExpressionEvaluatorFactory(
expressionParser, this, spELContext);
private final EntityInstantiators instantiators = new EntityInstantiators();
private final ElasticsearchTypeMapper typeMapper;
@@ -133,14 +121,6 @@ public class MappingElasticsearchConverter
}
}
@Override
public Environment getEnvironment() {
if (environment == null) {
environment = new StandardEnvironment();
}
return environment;
}
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
@@ -179,8 +159,7 @@ public class MappingElasticsearchConverter
@Override
public <R> R read(Class<R> type, Document source) {
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, expressionEvaluatorFactory,
instantiators);
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
return reader.read(type, source);
}
@@ -220,29 +199,29 @@ public class MappingElasticsearchConverter
*/
private static class Reader extends Base {
private final SpELContext spELContext;
private final EntityInstantiators instantiators;
private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory;
public Reader(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper,
CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory, EntityInstantiators instantiators) {
SpELContext spELContext, EntityInstantiators instantiators) {
super(mappingContext, conversionService, conversions, typeMapper);
this.expressionEvaluatorFactory = expressionEvaluatorFactory;
this.spELContext = spELContext;
this.instantiators = instantiators;
}
@SuppressWarnings("unchecked")
/**
* Reads the given source into the given type.
*
* @param type the type to convert the given source to.
* @param type they type to convert the given source to.
* @param source the source to create an object of the given type from.
* @return the object that was read
*/
<R> R read(Class<R> type, Document source) {
// noinspection unchecked
TypeInformation<R> typeInformation = TypeInformation.of((Class<R>) ClassUtils.getUserClass(type));
R r = read(typeInformation, source);
@@ -334,7 +313,8 @@ public class MappingElasticsearchConverter
private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ValueExpressionEvaluator evaluator = expressionEvaluatorFactory.create(source);
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext);
MapValueAccessor accessor = new MapValueAccessor(source);
InstanceCreatorMetadata<?> creatorMetadata = entity.getInstanceCreatorMetadata();
@@ -352,56 +332,50 @@ public class MappingElasticsearchConverter
return instance;
}
Document document = (source instanceof Document) ? (Document) source : null;
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator);
try {
R result = readProperties(targetEntity, instance, valueProvider);
R result = readProperties(targetEntity, instance, valueProvider);
if (document != null) {
if (document.hasId()) {
ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty();
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId());
}
}
if (document.hasVersion()) {
long version = document.getVersion();
ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
}
if (source instanceof Document document) {
if (document.hasId()) {
ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty();
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.isReadable() && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId());
}
}
if (source instanceof SearchDocument searchDocument) {
populateScriptFields(targetEntity, result, searchDocument);
if (document.hasVersion()) {
long version = document.getVersion();
ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn't request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
}
}
return result;
} catch (ConversionException e) {
String documentId = (document != null && document.hasId()) ? document.getId() : null;
throw new MappingConversionException(documentId, e);
}
if (source instanceof SearchDocument searchDocument) {
populateScriptFields(targetEntity, result, searchDocument);
}
return result;
}
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
ElasticsearchPersistentEntity<?> entity, MapValueAccessor source, ValueExpressionEvaluator evaluator) {
ElasticsearchPersistentEntity<?> entity, MapValueAccessor source, SpELExpressionEvaluator evaluator) {
ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator);
@@ -410,7 +384,7 @@ public class MappingElasticsearchConverter
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
entity, provider, null);
return new ConverterAwareValueExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
}
private boolean isAssignedSeqNo(long seqNo) {
@@ -492,7 +466,7 @@ public class MappingElasticsearchConverter
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
if (collectionComponentType != null) {
Object o = read(collectionComponentType, (Map<String, Object>) value);
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) read(type, (Map<String, Object>) value);
} else {
@@ -501,7 +475,7 @@ public class MappingElasticsearchConverter
if (collectionComponentType != null
&& collectionComponentType.isAssignableFrom(TypeInformation.of(value.getClass()))) {
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
return (o != null) ? getCollectionWithSingleElement(type, collectionComponentType, o) : null;
return getCollectionWithSingleElement(type, collectionComponentType, o);
}
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
@@ -519,7 +493,7 @@ public class MappingElasticsearchConverter
/**
* @param type the type to check
* @return the collection type if type is a collection, null otherwise,
* @return true if type is a collectoin, null otherwise,
*/
@Nullable
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
@@ -529,15 +503,17 @@ public class MappingElasticsearchConverter
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
if (source instanceof String[] strings) {
if (source instanceof String[]) {
// convert to a List
source = Arrays.asList(strings);
source = Arrays.asList((String[]) source);
}
if (source instanceof List<?> list) {
source = list.stream().map(it -> convertOnRead(propertyValueConverter, it)).collect(Collectors.toList());
} else if (source instanceof Set<?> set) {
source = set.stream().map(it -> convertOnRead(propertyValueConverter, it)).collect(Collectors.toSet());
if (source instanceof List) {
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toList());
} else if (source instanceof Set) {
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toSet());
} else {
source = convertOnRead(propertyValueConverter, source);
}
@@ -635,10 +611,9 @@ public class MappingElasticsearchConverter
* but will be removed from spring-data-commons, so we do it here
*/
@Nullable
private Object convertFromCollectionToObject(Object value, Class<?> target) {
private Object convertFromCollectionToObject(Object value, @Nullable Class<?> target) {
if (value.getClass().isArray()) {
// noinspection ArraysAsListWithZeroOrOneArgument
value = Arrays.asList(value);
}
@@ -688,9 +663,9 @@ public class MappingElasticsearchConverter
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor accessor;
final ValueExpressionEvaluator evaluator;
final SpELExpressionEvaluator evaluator;
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, ValueExpressionEvaluator evaluator) {
ElasticsearchPropertyValueProvider(MapValueAccessor accessor, SpELExpressionEvaluator evaluator) {
this.accessor = accessor;
this.evaluator = evaluator;
}
@@ -710,29 +685,33 @@ public class MappingElasticsearchConverter
}
/**
* Extension of {@link ValueExpressionParameterValueProvider} to recursively trigger value conversion on the raw
* Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw
* resolved SpEL value.
*
* @author Mark Paluch
*/
private class ConverterAwareValueExpressionParameterValueProvider
extends ValueExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
private class ConverterAwareSpELExpressionParameterValueProvider
extends SpELExpressionParameterValueProvider<ElasticsearchPersistentProperty> {
/**
* Creates a new {@link ConverterAwareValueExpressionParameterValueProvider}.
* Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}.
*
* @param evaluator must not be {@literal null}.
* @param conversionService must not be {@literal null}.
* @param delegate must not be {@literal null}.
*/
public ConverterAwareValueExpressionParameterValueProvider(ValueExpressionEvaluator evaluator,
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
ConversionService conversionService, ParameterValueProvider<ElasticsearchPersistentProperty> delegate) {
super(evaluator, conversionService, delegate);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Override
protected <T> T potentiallyConvertExpressionValue(Object object,
protected <T> T potentiallyConvertSpelValue(Object object,
Parameter<T, ElasticsearchPersistentProperty> parameter) {
return readValue(object, parameter.getType());
}
@@ -1009,8 +988,12 @@ public class MappingElasticsearchConverter
private static boolean hasEmptyValue(Object value) {
return value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|| value instanceof Map<?, ?> m && m.isEmpty();
if (value instanceof String s && s.isEmpty() || value instanceof Collection<?> c && c.isEmpty()
|| value instanceof Map<?, ?> m && m.isEmpty()) {
return true;
}
return false;
}
@SuppressWarnings("unchecked")
@@ -1412,18 +1395,12 @@ public class MappingElasticsearchConverter
if (properties.length > 1) {
var persistentProperty = persistentEntity.getPersistentProperty(propertyName);
if (persistentProperty != null) {
ElasticsearchPersistentEntity<?> nestedPersistentEntity = mappingContext
.getPersistentEntity(persistentProperty);
if (nestedPersistentEntity != null) {
return fieldName + '.' + updateFieldNames(properties[1], nestedPersistentEntity);
} else {
return fieldName;
}
}
return (persistentProperty != null)
? fieldName + "." + updateFieldNames(properties[1], mappingContext.getPersistentEntity(persistentProperty))
: fieldName;
} else {
return fieldName;
}
return fieldName;
} else {
return propertyPath;
}
@@ -1432,7 +1409,6 @@ public class MappingElasticsearchConverter
// endregion
@SuppressWarnings("ClassCanBeRecord")
static class MapValueAccessor {
final Map<String, Object> target;
@@ -18,7 +18,11 @@ package org.springframework.data.elasticsearch.core.document;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import org.springframework.data.elasticsearch.core.convert.ConversionException;
import org.springframework.data.elasticsearch.support.StringObjectMap;
@@ -26,12 +30,12 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A representation of an Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve
* original insertion order.
* A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve original
* insertion order.
* <p>
* Document does not allow {@code null} keys. It allows {@literal null} values.
* <p>
* Implementing classes can be either mutable or immutable. In case a subclass is immutable, its methods may throw
* Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw
* {@link UnsupportedOperationException} when calling modifying methods.
*
* @author Mark Paluch
@@ -56,7 +60,7 @@ public interface Document extends StringObjectMap<Document> {
* @param map source map containing key-value pairs and sub-documents. must not be {@literal null}.
* @return a new {@link Document}.
*/
static Document from(Map<String, ?> map) {
static Document from(Map<String, ? extends Object> map) {
Assert.notNull(map, "Map must not be null");
@@ -20,7 +20,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.data.elasticsearch.core.SearchShardStatistics;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.lang.Nullable;
@@ -28,7 +27,6 @@ import org.springframework.lang.Nullable;
* This represents the complete search response from Elasticsearch, including the returned documents.
*
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.0
*/
public class SearchDocumentResponse {
@@ -42,12 +40,10 @@ public class SearchDocumentResponse {
@Nullable private final Suggest suggest;
@Nullable String pointInTimeId;
@Nullable private final SearchShardStatistics searchShardStatistics;
public SearchDocumentResponse(long totalHits, String totalHitsRelation, float maxScore, @Nullable String scrollId,
@Nullable String pointInTimeId, List<SearchDocument> searchDocuments,
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest,
@Nullable SearchShardStatistics searchShardStatistics) {
@Nullable AggregationsContainer<?> aggregationsContainer, @Nullable Suggest suggest) {
this.totalHits = totalHits;
this.totalHitsRelation = totalHitsRelation;
this.maxScore = maxScore;
@@ -56,7 +52,6 @@ public class SearchDocumentResponse {
this.searchDocuments = searchDocuments;
this.aggregations = aggregationsContainer;
this.suggest = suggest;
this.searchShardStatistics = searchShardStatistics;
}
public long getTotalHits() {
@@ -98,11 +93,6 @@ public class SearchDocumentResponse {
return pointInTimeId;
}
@Nullable
public SearchShardStatistics getSearchShardStatistics() {
return searchShardStatistics;
}
/**
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
* imperative and the reactive code.
@@ -24,8 +24,8 @@ import org.springframework.data.geo.Box;
*/
public class GeoBox {
private final GeoPoint topLeft;
private final GeoPoint bottomRight;
private GeoPoint topLeft;
private GeoPoint bottomRight;
public GeoBox(GeoPoint topLeft, GeoPoint bottomRight) {
this.topLeft = topLeft;
@@ -33,7 +33,7 @@ public class GeoJsonMultiPolygon implements GeoJson<Iterable<GeoJsonPolygon>> {
public static final String TYPE = "MultiPolygon";
private final List<GeoJsonPolygon> coordinates = new ArrayList<>();
private List<GeoJsonPolygon> coordinates = new ArrayList<>();
private GeoJsonMultiPolygon(List<GeoJsonPolygon> polygons) {
this.coordinates.addAll(polygons);
@@ -214,9 +214,8 @@ public class MappingBuilder {
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
throws IOException {
var mappingAnnotation = entity != null ? entity.findAnnotation(Mapping.class) : null;
if (mappingAnnotation != null) {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
if (!mappingAnnotation.enabled()) {
objectNode.put(MAPPING_ENABLED, false);
@@ -290,16 +289,6 @@ public class MappingBuilder {
LOGGER.warn(String.format("error mapping property with name %s", property.getName()), e);
}
});
}
// write the alias entries after the properties
if (mappingAnnotation != null) {
for (MappingAlias mappingAlias : mappingAnnotation.aliases()) {
var aliasNode = propertiesNode.putObject(mappingAlias.name());
aliasNode.put(FIELD_PARAM_TYPE, FIELD_PARAM_TYPE_ALIAS);
aliasNode.put(FIELD_PARAM_PATH, mappingAlias.path());
}
}
}
@@ -443,7 +432,7 @@ public class MappingBuilder {
contextNode.put(FIELD_CONTEXT_NAME, context.name());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().getMappedName());
if (!context.precision().isEmpty()) {
if (context.precision().length() > 0) {
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
}
@@ -84,8 +84,6 @@ public final class MappingParameters {
static final String FIELD_PARAM_SIMILARITY = "similarity";
static final String FIELD_PARAM_TERM_VECTOR = "term_vector";
static final String FIELD_PARAM_TYPE = "type";
static final String FIELD_PARAM_PATH = "path";
static final String FIELD_PARAM_TYPE_ALIAS = "alias";
private final String analyzer;
private final boolean coerce;
@@ -171,8 +169,8 @@ public final class MappingParameters {
positiveScoreImpact = field.positiveScoreImpact();
dims = field.dims();
if (type == FieldType.Dense_Vector) {
Assert.isTrue(dims >= 1 && dims <= 4096,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
Assert.isTrue(dims >= 1 && dims <= 2048,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
}
Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object");
enabled = field.enabled();
@@ -214,8 +212,8 @@ public final class MappingParameters {
positiveScoreImpact = field.positiveScoreImpact();
dims = field.dims();
if (type == FieldType.Dense_Vector) {
Assert.isTrue(dims >= 1 && dims <= 4096,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 4096.");
Assert.isTrue(dims >= 1 && dims <= 2048,
"Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048.");
}
enabled = true;
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.core.join;
import java.util.Objects;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.lang.Nullable;
/**
@@ -39,7 +39,7 @@ public class JoinField<ID> {
this(name, null);
}
@PersistenceCreator
@PersistenceConstructor
public JoinField(String name, @Nullable ID parent) {
this.name = name;
this.parent = parent;
@@ -73,8 +73,8 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
private @Nullable ElasticsearchPersistentProperty indexedIndexNameProperty;
private @Nullable Document.VersionType versionType;
private final boolean createIndexAndMapping;
private final boolean alwaysWriteMapping;
private boolean createIndexAndMapping;
private boolean alwaysWriteMapping;
private final Dynamic dynamic;
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
@@ -230,17 +230,11 @@ public class ByQueryResponse {
return aborted;
}
@Nullable
public ElasticsearchErrorCause getElasticsearchErrorCause() {
return elasticsearchErrorCause;
}
/**
* Create a new {@link FailureBuilder} to build {@link Failure}
*
* @return a new {@link FailureBuilder} to build {@link Failure}
*/
public static FailureBuilder builder() {
return new FailureBuilder();
}
@@ -316,9 +316,6 @@ public class Criteria {
Criteria orCriteria = new OrCriteria(this.criteriaChain, criteria.getField());
orCriteria.queryCriteriaEntries.addAll(criteria.queryCriteriaEntries);
orCriteria.filterCriteriaEntries.addAll(criteria.filterCriteriaEntries);
orCriteria.subCriteria.addAll(criteria.subCriteria);
orCriteria.boost = criteria.boost;
orCriteria.negating = criteria.isNegating();
return orCriteria;
}
@@ -673,8 +670,8 @@ public class Criteria {
*/
public Criteria boundedBy(String topLeftGeohash, String bottomRightGeohash) {
Assert.isTrue(StringUtils.hasLength(topLeftGeohash), "topLeftGeohash must not be empty");
Assert.isTrue(StringUtils.hasLength(bottomRightGeohash), "bottomRightGeohash must not be empty");
Assert.isTrue(!StringUtils.isEmpty(topLeftGeohash), "topLeftGeohash must not be empty");
Assert.isTrue(!StringUtils.isEmpty(bottomRightGeohash), "bottomRightGeohash must not be empty");
filterCriteriaEntries
.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { topLeftGeohash, bottomRightGeohash }));
@@ -757,7 +754,7 @@ public class Criteria {
*/
public Criteria within(String geoLocation, String distance) {
Assert.isTrue(StringUtils.hasLength(geoLocation), "geoLocation value must not be null");
Assert.isTrue(!StringUtils.isEmpty(geoLocation), "geoLocation value must not be null");
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.WITHIN, new Object[] { geoLocation, distance }));
return this;
@@ -816,32 +813,6 @@ public class Criteria {
filterCriteriaEntries.add(new CriteriaEntry(OperationKey.GEO_CONTAINS, geoShape));
return this;
}
/**
* Adds a new filter CriteriaEntry for HAS_CHILD.
*
* @param query the has_child query.
* @return the current Criteria.
*/
public Criteria hasChild(HasChildQuery query) {
Assert.notNull(query, "has_child query must not be null.");
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_CHILD, query));
return this;
}
/**
* Adds a new filter CriteriaEntry for HAS_PARENT.
*
* @param query the has_parent query.
* @return the current Criteria.
*/
public Criteria hasParent(HasParentQuery query) {
Assert.notNull(query, "has_parent query must not be null.");
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.HAS_PARENT, query));
return this;
}
// endregion
// region helper functions
@@ -1003,11 +974,7 @@ public class Criteria {
/**
* @since 5.1
*/
REGEXP,
/**
* @since 5.3
*/
HAS_CHILD, HAS_PARENT;
REGEXP;
/**
* @return true if this key does not have an associated value
@@ -1,676 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.Query.SearchType;
import org.springframework.data.elasticsearch.core.query.types.ConflictsType;
import org.springframework.data.elasticsearch.core.query.types.OperatorType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a delete request.
*
* @author Aouichaoui Youssef
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html">docs</a>
* @since 5.3
*/
public class DeleteQuery {
// For Lucene query
/**
* Query in the Lucene query string syntax.
*/
@Nullable private final String q;
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false. This parameter can only be used when the
* lucene query {@code q} parameter is specified.
*/
@Nullable private final Boolean analyzeWildcard;
/**
* Analyzer to use for the query string. This parameter can only be used when the lucene query {@code q} parameter is
* specified.
*/
@Nullable private final String analyzer;
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}. This
* parameter can only be used when the lucene query {@code q} parameter is specified.
*/
@Nullable private final OperatorType defaultOperator;
/**
* Field to be used as the default when no field prefix is specified in the query string. This parameter can only be
* used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
@Nullable private final String df;
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless
* specified otherwise. By default, this feature is turned off.
*/
@Nullable private final Boolean lenient;
// For ES query
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression, an
* index alias, or the {@literal _all value} only targets missing or closed indices. By default, this is set to
* {@code true}.
*/
@Nullable private final Boolean allowNoIndices;
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. Defaults to
* abort.
*/
@Nullable private final ConflictsType conflicts;
/**
* Type of index that wildcard patterns can match. Defaults to {@literal open}.
*/
@Nullable private final EnumSet<IndicesOptions.WildcardStates> expandWildcards;
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}. By default, this
* is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnavailable;
/**
* Maximum number of documents to process. Defaults to all documents.
*/
@Nullable private final Long maxDocs;
/**
* Specifies the node or shard the operation should be performed on.
*/
@Nullable private final String preference;
/**
* Use the request cache when it is {@code true}. By default, use the index-level setting.
*/
@Nullable private final Boolean requestCache;
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}. By
* default, this is set to {@code false}.
*/
@Nullable private final Boolean refresh;
/**
* Limited this request to a certain number of sub-requests per second. By default, this is set to {@code -1} (no
* throttle).
*/
@Nullable private final Float requestsPerSecond;
/**
* Custom value used to route operations to a specific shard.
*/
@Nullable private final String routing;
/**
* Period to retain the search context for scrolling.
*/
@Nullable private final Duration scroll;
/**
* Size of the scroll request that powers the operation. By default, this is set to {@code 1000}.
*/
@Nullable private final Long scrollSize;
/**
* The type of the search operation.
*/
@Nullable private final SearchType searchType;
/**
* Explicit timeout for each search request. By default, this is set to no timeout.
*/
@Nullable private final Duration searchTimeout;
/**
* The number of slices this task should be divided into. By default, this is set to {@code 1} meaning the task isnt
* sliced into subtasks.
*/
@Nullable private final Integer slices;
/**
* Sort search results in a specific order.
*/
@Nullable private final Sort sort;
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
@Nullable private final List<String> stats;
/**
* The Maximum number of documents that can be collected for each shard. If a query exceeds this limit, Elasticsearch
* will stop the query.
*/
@Nullable private final Long terminateAfter;
/**
* Period each deletion request waits for active shards. By default, this is set to {@code 1m} (one minute).
*/
@Nullable private final Duration timeout;
/**
* Returns the document version as part of a hit.
*/
@Nullable private final Boolean version;
// Body
/**
* Query that specifies the documents to delete.
*/
private final Query query;
public static Builder builder(Query query) {
return new Builder(query);
}
private DeleteQuery(Builder builder) {
this.q = builder.luceneQuery;
this.analyzeWildcard = builder.analyzeWildcard;
this.analyzer = builder.analyzer;
this.defaultOperator = builder.defaultOperator;
this.df = builder.defaultField;
this.lenient = builder.lenient;
this.allowNoIndices = builder.allowNoIndices;
this.conflicts = builder.conflicts;
this.expandWildcards = builder.expandWildcards;
this.ignoreUnavailable = builder.ignoreUnavailable;
this.maxDocs = builder.maxDocs;
this.preference = builder.preference;
this.requestCache = builder.requestCache;
this.refresh = builder.refresh;
this.requestsPerSecond = builder.requestsPerSecond;
this.routing = builder.routing;
this.scroll = builder.scrollTime;
this.scrollSize = builder.scrollSize;
this.searchType = builder.searchType;
this.searchTimeout = builder.searchTimeout;
this.slices = builder.slices;
this.sort = builder.sort;
this.stats = builder.stats;
this.terminateAfter = builder.terminateAfter;
this.timeout = builder.timeout;
this.version = builder.version;
this.query = builder.query;
}
@Nullable
public String getQ() {
return q;
}
@Nullable
public Boolean getAnalyzeWildcard() {
return analyzeWildcard;
}
@Nullable
public String getAnalyzer() {
return analyzer;
}
@Nullable
public OperatorType getDefaultOperator() {
return defaultOperator;
}
@Nullable
public String getDf() {
return df;
}
@Nullable
public Boolean getLenient() {
return lenient;
}
@Nullable
public Boolean getAllowNoIndices() {
return allowNoIndices;
}
@Nullable
public ConflictsType getConflicts() {
return conflicts;
}
@Nullable
public EnumSet<IndicesOptions.WildcardStates> getExpandWildcards() {
return expandWildcards;
}
@Nullable
public Boolean getIgnoreUnavailable() {
return ignoreUnavailable;
}
@Nullable
public Long getMaxDocs() {
return maxDocs;
}
@Nullable
public String getPreference() {
return preference;
}
@Nullable
public Boolean getRequestCache() {
return requestCache;
}
@Nullable
public Boolean getRefresh() {
return refresh;
}
@Nullable
public Float getRequestsPerSecond() {
return requestsPerSecond;
}
@Nullable
public String getRouting() {
return routing;
}
@Nullable
public Duration getScroll() {
return scroll;
}
@Nullable
public Long getScrollSize() {
return scrollSize;
}
@Nullable
public SearchType getSearchType() {
return searchType;
}
@Nullable
public Duration getSearchTimeout() {
return searchTimeout;
}
@Nullable
public Integer getSlices() {
return slices;
}
@Nullable
public Sort getSort() {
return sort;
}
@Nullable
public List<String> getStats() {
return stats;
}
@Nullable
public Long getTerminateAfter() {
return terminateAfter;
}
@Nullable
public Duration getTimeout() {
return timeout;
}
@Nullable
public Boolean getVersion() {
return version;
}
@Nullable
public Query getQuery() {
return query;
}
public static final class Builder {
// For Lucene query
@Nullable private String luceneQuery;
@Nullable private Boolean analyzeWildcard;
@Nullable private String analyzer;
@Nullable private OperatorType defaultOperator;
@Nullable private String defaultField;
@Nullable private Boolean lenient;
// For ES query
@Nullable private Boolean allowNoIndices;
@Nullable private ConflictsType conflicts;
@Nullable private EnumSet<IndicesOptions.WildcardStates> expandWildcards;
@Nullable private Boolean ignoreUnavailable;
@Nullable private Long maxDocs;
@Nullable private String preference;
@Nullable private Boolean requestCache;
@Nullable private Boolean refresh;
@Nullable private Float requestsPerSecond;
@Nullable private String routing;
@Nullable private Duration scrollTime;
@Nullable private Long scrollSize;
@Nullable private SearchType searchType;
@Nullable private Duration searchTimeout;
@Nullable private Integer slices;
@Nullable private Sort sort;
@Nullable private List<String> stats;
@Nullable private Long terminateAfter;
@Nullable private Duration timeout;
@Nullable private Boolean version;
// Body
private final Query query;
private Builder(Query query) {
Assert.notNull(query, "query must not be null");
this.query = query;
}
/**
* Query in the Lucene query string syntax.
*/
public Builder withLuceneQuery(@Nullable String luceneQuery) {
this.luceneQuery = luceneQuery;
return this;
}
/**
* If true, wildcard and prefix queries are analyzed. Defaults to false. This parameter can only be used when the
* lucene query {@code q} parameter is specified.
*/
public Builder withAnalyzeWildcard(@Nullable Boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Analyzer to use for the query string. This parameter can only be used when the lucene query {@code q} parameter
* is specified.
*/
public Builder withAnalyzer(@Nullable String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* The default operator for a query string query: {@literal AND} or {@literal OR}. Defaults to {@literal OR}. This
* parameter can only be used when the lucene query {@code q} parameter is specified.
*/
public Builder withDefaultOperator(@Nullable OperatorType defaultOperator) {
this.defaultOperator = defaultOperator;
return this;
}
/**
* Field to be used as the default when no field prefix is specified in the query string. This parameter can only be
* used when the lucene query {@code q} parameter is specified.
* <p>
* e.g: {@code {"query":{"prefix":{"user.name":{"value":"es"}}}} }
*/
public Builder withDefaultField(@Nullable String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* If a query contains errors related to the format of the data being entered, they will be disregarded unless
* specified otherwise. By default, this feature is turned off.
*/
public Builder withLenient(@Nullable Boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* An error will occur if the condition is {@code false} and any of the following are true: a wildcard expression,
* an index alias, or the {@literal _all value} only targets missing or closed indices. By default, this is set to
* {@code true}.
*/
public Builder withAllowNoIndices(@Nullable Boolean allowNoIndices) {
this.allowNoIndices = allowNoIndices;
return this;
}
/**
* Define the types of conflicts that occur when a query encounters version conflicts: abort or proceed. Defaults to
* abort.
*/
public Builder withConflicts(@Nullable ConflictsType conflicts) {
this.conflicts = conflicts;
return this;
}
/**
* Type of index that wildcard patterns can match. Defaults to {@literal open}.
*/
public Builder setExpandWildcards(@Nullable EnumSet<IndicesOptions.WildcardStates> expandWildcards) {
this.expandWildcards = expandWildcards;
return this;
}
/**
* An error occurs if it is directed at an index that is missing or closed when it is {@code false}. By default,
* this is set to {@code false}.
*/
public Builder withIgnoreUnavailable(@Nullable Boolean ignoreUnavailable) {
this.ignoreUnavailable = ignoreUnavailable;
return this;
}
/**
* Maximum number of documents to process. Defaults to all documents.
*/
public Builder withMaxDocs(@Nullable Long maxDocs) {
this.maxDocs = maxDocs;
return this;
}
/**
* Specifies the node or shard the operation should be performed on.
*/
public Builder withPreference(@Nullable String preference) {
this.preference = preference;
return this;
}
/**
* Use the request cache when it is {@code true}. By default, use the index-level setting.
*/
public Builder withRequestCache(@Nullable Boolean requestCache) {
this.requestCache = requestCache;
return this;
}
/**
* Refreshes all shards involved in the deleting by query after the request completes when it is {@code true}. By
* default, this is set to {@code false}.
*/
public Builder withRefresh(@Nullable Boolean refresh) {
this.refresh = refresh;
return this;
}
/**
* Limited this request to a certain number of sub-requests per second. By default, this is set to {@code -1} (no
* throttle).
*/
public Builder withRequestsPerSecond(@Nullable Float requestsPerSecond) {
this.requestsPerSecond = requestsPerSecond;
return this;
}
/**
* Custom value used to route operations to a specific shard.
*/
public Builder withRouting(@Nullable String routing) {
this.routing = routing;
return this;
}
/**
* Period to retain the search context for scrolling.
*/
public Builder withScrollTime(@Nullable Duration scrollTime) {
this.scrollTime = scrollTime;
return this;
}
/**
* Size of the scroll request that powers the operation. By default, this is set to {@code 1000}.
*/
public Builder withScrollSize(@Nullable Long scrollSize) {
this.scrollSize = scrollSize;
return this;
}
/**
* The type of the search operation.
*/
public Builder withSearchType(@Nullable SearchType searchType) {
this.searchType = searchType;
return this;
}
/**
* Explicit timeout for each search request. By default, this is set to no timeout.
*/
public Builder withSearchTimeout(@Nullable Duration searchTimeout) {
this.searchTimeout = searchTimeout;
return this;
}
/**
* The number of slices this task should be divided into. By default, this is set to {@code 1} meaning the task
* isnt sliced into subtasks.
*/
public Builder withSlices(@Nullable Integer slices) {
this.slices = slices;
return this;
}
/**
* Sort search results in a specific order.
*/
public Builder withSort(@Nullable Sort sort) {
this.sort = sort;
return this;
}
/**
* Specific {@code tag} of the request for logging and statistical purposes.
*/
public Builder withStats(@Nullable List<String> stats) {
this.stats = stats;
return this;
}
/**
* The Maximum number of documents that can be collected for each shard. If a query exceeds this limit,
* Elasticsearch will stop the query.
*/
public Builder withTerminateAfter(@Nullable Long terminateAfter) {
this.terminateAfter = terminateAfter;
return this;
}
/**
* Period each deletion request waits for active shards. By default, this is set to {@code 1m} (one minute).
*/
public Builder withTimeout(@Nullable Duration timeout) {
this.timeout = timeout;
return this;
}
/**
* Returns the document version as part of a hit.
*/
public Builder withVersion(@Nullable Boolean version) {
this.version = version;
return this;
}
public DeleteQuery build() {
if (luceneQuery == null) {
if (defaultField != null) {
throw new IllegalArgumentException("When defining the df parameter, you must include the Lucene query.");
}
if (analyzer != null) {
throw new IllegalArgumentException(
"When defining the analyzer parameter, you must include the Lucene query.");
}
if (analyzeWildcard != null) {
throw new IllegalArgumentException(
"When defining the analyzeWildcard parameter, you must include the Lucene query.");
}
if (defaultOperator != null) {
throw new IllegalArgumentException(
"When defining the defaultOperator parameter, you must include the Lucene query.");
}
if (lenient != null) {
throw new IllegalArgumentException("When defining the lenient parameter, you must include the Lucene query.");
}
}
return new DeleteQuery(this);
}
}
}
@@ -1,204 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a has_child request.
*
* @author Aouichaoui Youssef
* @see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-query.html">docs</a>
* @since 5.3
*/
public class HasChildQuery {
/**
* Name of the child relationship mapped for the join field.
*/
private final String type;
/**
* Query that specifies the documents to run on child documents of the {@link #type} field.
*/
private final Query query;
/**
* Indicates whether to ignore an unmapped {@link #type} and not return any documents instead of an error. Default,
* this is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnmapped;
/**
* The Maximum number of child documents that match the {@link #query} allowed for a returned parent document. If the
* parent document exceeds this limit, it is excluded from the search results.
*/
@Nullable private final Integer maxChildren;
/**
* Minimum number of child documents that match the query required to match the {@link #query} for a returned parent
* document. If the parent document does not meet this limit, it is excluded from the search results.
*/
@Nullable private final Integer minChildren;
/**
* Indicates how scores for matching child documents affect the root parent documents relevance score.
*/
@Nullable private final ScoreMode scoreMode;
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
@Nullable private final InnerHitsQuery innerHitsQuery;
public static Builder builder(String type) {
return new Builder(type);
}
private HasChildQuery(Builder builder) {
this.type = builder.type;
this.query = builder.query;
this.innerHitsQuery = builder.innerHitsQuery;
this.ignoreUnmapped = builder.ignoreUnmapped;
this.maxChildren = builder.maxChildren;
this.minChildren = builder.minChildren;
this.scoreMode = builder.scoreMode;
}
public String getType() {
return type;
}
public Query getQuery() {
return query;
}
@Nullable
public Boolean getIgnoreUnmapped() {
return ignoreUnmapped;
}
@Nullable
public Integer getMaxChildren() {
return maxChildren;
}
@Nullable
public Integer getMinChildren() {
return minChildren;
}
@Nullable
public ScoreMode getScoreMode() {
return scoreMode;
}
@Nullable
public InnerHitsQuery getInnerHitsQuery() {
return innerHitsQuery;
}
public enum ScoreMode {
Default, Avg, Max, Min, Sum
}
public static final class Builder {
private final String type;
private Query query;
@Nullable private Boolean ignoreUnmapped;
@Nullable private Integer maxChildren;
@Nullable private Integer minChildren;
@Nullable private ScoreMode scoreMode;
@Nullable private InnerHitsQuery innerHitsQuery;
private Builder(String type) {
Assert.notNull(type, "type must not be null");
this.type = type;
}
/**
* Query that specifies the documents to run on child documents of the {@link #type} field.
*/
public Builder withQuery(Query query) {
this.query = query;
return this;
}
/**
* Indicates whether to ignore an unmapped {@link #type} and not return any documents instead of an error. Default,
* this is set to {@code false}.
*/
public Builder withIgnoreUnmapped(@Nullable Boolean ignoreUnmapped) {
this.ignoreUnmapped = ignoreUnmapped;
return this;
}
/**
* The Maximum number of child documents that match the {@link #query} allowed for a returned parent document. If
* the parent document exceeds this limit, it is excluded from the search results.
*/
public Builder withMaxChildren(@Nullable Integer maxChildren) {
this.maxChildren = maxChildren;
return this;
}
/**
* Minimum number of child documents that match the query required to match the {@link #query} for a returned parent
* document. If the parent document does not meet this limit, it is excluded from the search results.
*/
public Builder withMinChildren(@Nullable Integer minChildren) {
this.minChildren = minChildren;
return this;
}
/**
* Indicates how scores for matching child documents affect the root parent documents relevance score.
*/
public Builder withScoreMode(@Nullable ScoreMode scoreMode) {
this.scoreMode = scoreMode;
return this;
}
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
public Builder withInnerHitsQuery(@Nullable InnerHitsQuery innerHitsQuery) {
this.innerHitsQuery = innerHitsQuery;
return this;
}
public HasChildQuery build() {
Assert.notNull(query, "query must not be null.");
return new HasChildQuery(this);
}
}
}
@@ -1,141 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Defines a has_parent request.
*
* @author Aouichaoui Youssef
* @see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-parent-query.html">docs</a>
* @since 5.3
*/
public class HasParentQuery {
/**
* Name of the parent relationship mapped for the join field.
*/
private final String parentType;
/**
* Query that specifies the documents to run on parent documents of the {@link #parentType} field.
*/
private final Query query;
/**
* Indicates whether the relevance score of a matching parent document is aggregated into its child documents.
* Default, this is set to {@code false}.
*/
@Nullable private final Boolean score;
/**
* Indicates whether to ignore an unmapped {@link #parentType} and not return any documents instead of an error.
* Default, this is set to {@code false}.
*/
@Nullable private final Boolean ignoreUnmapped;
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
@Nullable private final InnerHitsQuery innerHitsQuery;
public static Builder builder(String parentType) {
return new Builder(parentType);
}
private HasParentQuery(Builder builder) {
this.parentType = builder.parentType;
this.query = builder.query;
this.innerHitsQuery = builder.innerHitsQuery;
this.score = builder.score;
this.ignoreUnmapped = builder.ignoreUnmapped;
}
public String getParentType() {
return parentType;
}
public Query getQuery() {
return query;
}
@Nullable
public Boolean getScore() {
return score;
}
@Nullable
public Boolean getIgnoreUnmapped() {
return ignoreUnmapped;
}
@Nullable
public InnerHitsQuery getInnerHitsQuery() {
return innerHitsQuery;
}
public static class Builder {
private final String parentType;
private Query query;
@Nullable private Boolean score;
@Nullable private Boolean ignoreUnmapped;
@Nullable private InnerHitsQuery innerHitsQuery;
private Builder(String parentType) {
Assert.notNull(parentType, "parent_type must not be null.");
this.parentType = parentType;
}
public Builder withQuery(Query query) {
this.query = query;
return this;
}
public Builder withScore(@Nullable Boolean score) {
this.score = score;
return this;
}
public Builder withIgnoreUnmapped(@Nullable Boolean ignoreUnmapped) {
this.ignoreUnmapped = ignoreUnmapped;
return this;
}
/**
* Obtaining nested objects and documents that have a parent-child relationship.
*/
public Builder withInnerHitsQuery(@Nullable InnerHitsQuery innerHitsQuery) {
this.innerHitsQuery = innerHitsQuery;
return this;
}
public HasParentQuery build() {
Assert.notNull(query, "query must not be null.");
return new HasParentQuery(this);
}
}
}
@@ -23,8 +23,8 @@ package org.springframework.data.elasticsearch.core.query;
*/
public class IndexBoost {
private final String indexName;
private final float boost;
private String indexName;
private float boost;
public IndexBoost(String indexName, float boost) {
this.indexName = indexName;
@@ -25,8 +25,8 @@ import java.util.EnumSet;
*/
public class IndicesOptions {
private final EnumSet<Option> options;
private final EnumSet<WildcardStates> expandWildcards;
private EnumSet<Option> options;
private EnumSet<WildcardStates> expandWildcards;
public static final IndicesOptions STRICT_EXPAND_OPEN = new IndicesOptions(
EnumSet.of(IndicesOptions.Option.ALLOW_NO_INDICES), EnumSet.of(IndicesOptions.WildcardStates.OPEN));
@@ -101,10 +101,10 @@ public class IndicesOptions {
}
public enum WildcardStates {
OPEN, CLOSED, HIDDEN, ALL, NONE
OPEN, CLOSED, HIDDEN, ALL, NONE;
}
public enum Option {
IGNORE_UNAVAILABLE, IGNORE_ALIASES, ALLOW_NO_INDICES, FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_THROTTLED
IGNORE_UNAVAILABLE, IGNORE_ALIASES, ALLOW_NO_INDICES, FORBID_ALIASES_TO_MULTIPLE_INDICES, FORBID_CLOSED_INDICES, IGNORE_THROTTLED;
}
}
@@ -1,107 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query;
import org.springframework.lang.Nullable;
/**
* Defines an inner_hits request.
*
* @author Aouichaoui Youssef
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inner-hits.html">docs</a>
* @since 5.3
*/
public class InnerHitsQuery {
/**
* The name to be used for the particular inner hit definition in the response.
*/
@Nullable private final String name;
/**
* The maximum number of hits to return.
*/
@Nullable private final Integer size;
/**
* The offset from where the first hit to fetch.
*/
@Nullable private final Integer from;
public static Builder builder() {
return new Builder();
}
private InnerHitsQuery(Builder builder) {
this.name = builder.name;
this.from = builder.from;
this.size = builder.size;
}
@Nullable
public String getName() {
return name;
}
@Nullable
public Integer getSize() {
return size;
}
@Nullable
public Integer getFrom() {
return from;
}
public static final class Builder {
@Nullable private String name;
@Nullable private Integer size;
@Nullable private Integer from;
private Builder() {}
/**
* The name to be used for the particular inner hit definition in the response.
*/
public Builder withName(@Nullable String name) {
this.name = name;
return this;
}
/**
* The maximum number of hits to return.
*/
public Builder withSize(@Nullable Integer size) {
this.size = size;
return this;
}
/**
* The offset from where the first hit to fetch.
*/
public Builder withFrom(@Nullable Integer from) {
this.from = from;
return this;
}
public InnerHitsQuery build() {
return new InnerHitsQuery(this);
}
}
}
@@ -15,12 +15,13 @@
*/
package org.springframework.data.elasticsearch.core.query;
import java.util.function.Function;
import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Extends the {@link Sort.Order} with properties that can be set on Elasticsearch order options.
*
@@ -69,7 +70,7 @@ public class Order extends Sort.Order {
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, @Nullable Mode mode,
@Nullable String unmappedType) {
this(direction, property, nullHandlingHint, mode, unmappedType, null);
this(direction, property, nullHandlingHint, null, unmappedType, null);
}
public Order(Sort.Direction direction, String property, Sort.NullHandling nullHandlingHint, @Nullable Mode mode,
@@ -142,19 +143,18 @@ public class Order extends Sort.Order {
}
public static class Nested {
private final String path;
@Nullable private final Query filter;
private String path;
@Nullable private Query filter;
@Nullable private Integer maxChildren = null;
@Nullable private final Nested nested;
@Nullable private Nested nested;
public static Nested of(String path, Function<Nested.Builder, Nested.Builder> builderFunction) {
Assert.notNull(path, "path must not be null");
Assert.notNull(builderFunction, "builderFunction must not be null");
Assert.notNull(path, "path must not be null");
Assert.notNull(builderFunction, "builderFunction must not be null");
return builderFunction.apply(builder(path)).build();
return builderFunction.apply(builder(path)).build();
}
public Nested(String path, @Nullable Query filter, @Nullable Integer maxChildren, @Nullable Nested nested) {
Assert.notNull(path, "path must not be null");
@@ -189,7 +189,7 @@ public class Order extends Sort.Order {
}
public static class Builder {
private final String path;
private String path;
@Nullable private Query filter = null;
@Nullable private Integer maxChildren = null;
@Nullable private Nested nested = null;
@@ -203,9 +203,8 @@ public class Order extends Sort.Order {
/**
* Sets the filter query for a nested sort.<br/>
* Note: This cannot be a {@link CriteriaQuery}, as that would be sent as a nested query within the filter, use a
* {@link org.springframework.data.elasticsearch.client.elc.NativeQuery} or {@link StringQuery} instead.
*
* Note: This cannot be a {@link CriteriaQuery}, as that would be sent as a nested query within the filter,
* use a {@link org.springframework.data.elasticsearch.client.elc.NativeQuery} or {@link StringQuery} instead.
* @param filter the filter to set
* @return this builder
* @throws IllegalArgumentException when a {@link CriteriaQuery} is passed.
@@ -447,7 +447,7 @@ public interface Query {
@Nullable
default PointInTime getPointInTime() {
return null;
}
};
/**
* returns the number of documents that are requested when the reactive code does a batched search operation. This is
@@ -469,8 +469,7 @@ public interface Query {
/**
* @since 5.1
*/
@Nullable
EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
@Nullable EnumSet<IndicesOptions.WildcardStates> getExpandWildcards();
/**
* @return a possible empty list of docvalue_field values to be set on the query.
@@ -19,8 +19,6 @@ import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.util.Objects;
/**
* The most trivial implementation of a Field. The {@link #name} is updatable, so it may be changed during query
* preparation by the {@link org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter}.
@@ -81,16 +79,4 @@ public class SimpleField implements Field {
public String toString() {
return getName();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SimpleField that)) return false;
return Objects.equals(name, that.name) && Objects.equals(fieldType, that.fieldType) && Objects.equals(path, that.path);
}
@Override
public int hashCode() {
return Objects.hash(name, fieldType, path);
}
}
@@ -26,7 +26,7 @@ import org.springframework.util.Assert;
*/
public class UpdateResponse {
private final Result result;
private Result result;
public UpdateResponse(Result result) {
@@ -47,6 +47,6 @@ public class UpdateResponse {
}
public enum Result {
CREATED, UPDATED, DELETED, NOT_FOUND, NOOP
CREATED, UPDATED, DELETED, NOT_FOUND, NOOP;
}
}
@@ -15,13 +15,14 @@
*/
package org.springframework.data.elasticsearch.core.query.highlight;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.3
*/
public class Highlight {
@@ -56,4 +57,42 @@ public class Highlight {
public List<HighlightField> getFields() {
return fields;
}
/**
* Creates a {@link Highlight} from an Annotation instance.
*
* @param highlight must not be {@literal null}
* @return highlight definition
*/
public static Highlight of(org.springframework.data.elasticsearch.annotations.Highlight highlight) {
Assert.notNull(highlight, "highlight must not be null");
org.springframework.data.elasticsearch.annotations.HighlightParameters parameters = highlight.parameters();
HighlightParameters highlightParameters = HighlightParameters.builder() //
.withBoundaryChars(parameters.boundaryChars()) //
.withBoundaryMaxScan(parameters.boundaryMaxScan()) //
.withBoundaryScanner(parameters.boundaryScanner()) //
.withBoundaryScannerLocale(parameters.boundaryScannerLocale()) //
.withEncoder(parameters.encoder()) //
.withForceSource(parameters.forceSource()) //
.withFragmenter(parameters.fragmenter()) //
.withFragmentSize(parameters.fragmentSize()) //
.withNoMatchSize(parameters.noMatchSize()) //
.withNumberOfFragments(parameters.numberOfFragments()) //
.withOrder(parameters.order()) //
.withPhraseLimit(parameters.phraseLimit()) //
.withPreTags(parameters.preTags()) //
.withPostTags(parameters.postTags()) //
.withRequireFieldMatch(parameters.requireFieldMatch()) //
.withTagsSchema(parameters.tagsSchema()) //
.withType(parameters.type()) //
.build();
List<HighlightField> highlightFields = Arrays.stream(highlight.fields()) //
.map(HighlightField::of) //
.collect(Collectors.toList());
return new Highlight(highlightParameters, highlightFields);
}
}
@@ -15,13 +15,10 @@
*/
package org.springframework.data.elasticsearch.core.query.highlight;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 4.3
*/
public abstract class HighlightCommonParameters {
@@ -34,7 +31,6 @@ public abstract class HighlightCommonParameters {
private final int fragmentSize;
private final int noMatchSize;
private final int numberOfFragments;
@Nullable private final Query highlightQuery;
private final String order;
private final int phraseLimit;
private final String[] preTags;
@@ -55,7 +51,6 @@ public abstract class HighlightCommonParameters {
fragmentSize = builder.fragmentSize;
noMatchSize = builder.noMatchSize;
numberOfFragments = builder.numberOfFragments;
highlightQuery = builder.highlightQuery;
order = builder.order;
phraseLimit = builder.phraseLimit;
preTags = builder.preTags;
@@ -100,11 +95,6 @@ public abstract class HighlightCommonParameters {
return numberOfFragments;
}
@Nullable
public Query getHighlightQuery() {
return highlightQuery;
}
public String getOrder() {
return order;
}
@@ -140,10 +130,6 @@ public abstract class HighlightCommonParameters {
private int fragmentSize = -1;
private int noMatchSize = -1;
private int numberOfFragments = -1;
/**
* Only the search query part of the {@link Query} takes effect, others are just ignored.
*/
@Nullable private Query highlightQuery = null;
private String order = "";
private int phraseLimit = -1;
private String[] preTags = new String[0];
@@ -198,11 +184,6 @@ public abstract class HighlightCommonParameters {
return (SELF) this;
}
public SELF withHighlightQuery(@Nullable Query highlightQuery) {
this.highlightQuery = highlightQuery;
return (SELF) this;
}
public SELF withOrder(String order) {
this.order = order;
return (SELF) this;
@@ -1,26 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query.types;
/**
* Define the types of conflicts that occur when a query encounters version conflicts.
*
* @author Aouichaoui Youssef
* @since 5.3
*/
public enum ConflictsType {
Abort, Proceed
}
@@ -1,26 +0,0 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.query.types;
/**
* Define the default operator for a query string query.
*
* @author Aouichaoui Youssef
* @since 5.3
*/
public enum OperatorType {
And, Or
}
@@ -1,3 +0,0 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.query.types;
@@ -19,7 +19,6 @@ import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -42,7 +41,6 @@ import org.w3c.dom.Element;
* @author Mohsin Husen
* @author Mark Paluch
* @author Christoph Strobl
* @author Junghoon Ban
*/
public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
@@ -108,7 +106,7 @@ public class ElasticsearchRepositoryConfigExtension extends RepositoryConfigurat
*/
@Override
protected Collection<Class<?>> getIdentifyingTypes() {
return List.of(ElasticsearchRepository.class);
return Arrays.asList(ElasticsearchRepository.class, ElasticsearchRepository.class);
}
/*
@@ -15,18 +15,20 @@
*/
package org.springframework.data.elasticsearch.repository.query;
import java.util.Collections;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHitsImpl;
import org.springframework.data.elasticsearch.core.TotalHitsRelation;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.util.StreamUtils;
@@ -40,7 +42,6 @@ import org.springframework.util.ClassUtils;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Haibo Liu
*/
public abstract class AbstractElasticsearchRepositoryQuery implements RepositoryQuery {
@@ -49,20 +50,12 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
protected ElasticsearchQueryMethod queryMethod;
protected final ElasticsearchOperations elasticsearchOperations;
protected final ElasticsearchConverter elasticsearchConverter;
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
public AbstractElasticsearchRepositoryQuery(ElasticsearchQueryMethod queryMethod,
ElasticsearchOperations elasticsearchOperations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
Assert.notNull(queryMethod, "queryMethod must not be null");
Assert.notNull(elasticsearchOperations, "elasticsearchOperations must not be null");
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
ElasticsearchOperations elasticsearchOperations) {
this.queryMethod = queryMethod;
this.elasticsearchOperations = elasticsearchOperations;
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
this.evaluationContextProvider = evaluationContextProvider;
}
@Override
@@ -95,7 +88,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
if (isDeleteQuery()) {
result = countOrGetDocumentsForDelete(query, parameterAccessor);
elasticsearchOperations.delete(DeleteQuery.builder(query).build(), clazz, index);
elasticsearchOperations.delete(query, clazz, index);
elasticsearchOperations.indexOps(index).refresh();
} else if (isCountQuery()) {
result = elasticsearchOperations.count(query, clazz, index);
@@ -114,13 +107,24 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
: PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(query, clazz, index));
} else if (queryMethod.isCollectionQuery()) {
if (parameterAccessor.getPageable().isUnpaged()) {
int itemCount = (int) elasticsearchOperations.count(query, clazz, index);
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
if (itemCount == 0) {
result = new SearchHitsImpl<>(0, TotalHitsRelation.EQUAL_TO, Float.NaN, null,
query.getPointInTime() != null ? query.getPointInTime().id() : null, Collections.emptyList(), null, null);
} else {
query.setPageable(PageRequest.of(0, Math.max(1, itemCount)));
}
} else {
query.setPageable(parameterAccessor.getPageable());
}
result = elasticsearchOperations.search(query, clazz, index);
if (result == null) {
result = elasticsearchOperations.search(query, clazz, index);
}
} else {
result = elasticsearchOperations.searchOne(query, clazz, index);
}
@@ -133,12 +137,12 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
public Query createQuery(Object[] parameters) {
ElasticsearchParametersParameterAccessor parameterAccessor = getParameterAccessor(parameters);
ResultProcessor resultProcessor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
var query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
evaluationContextProvider);
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
return query;
}
@@ -28,14 +28,12 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.DeleteQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingConverter;
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryExecution.ResultProcessingExecution;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.util.Assert;
@@ -45,26 +43,18 @@ import org.springframework.util.Assert;
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Haibo Liu
* @since 3.2
*/
abstract class AbstractReactiveElasticsearchRepositoryQuery implements RepositoryQuery {
protected final ReactiveElasticsearchQueryMethod queryMethod;
private final ReactiveElasticsearchOperations elasticsearchOperations;
protected final QueryMethodEvaluationContextProvider evaluationContextProvider;
AbstractReactiveElasticsearchRepositoryQuery(ReactiveElasticsearchQueryMethod queryMethod,
ReactiveElasticsearchOperations elasticsearchOperations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
Assert.notNull(queryMethod, "queryMethod must not be null");
Assert.notNull(elasticsearchOperations, "elasticsearchOperations must not be null");
Assert.notNull(evaluationContextProvider, "evaluationContextProvider must not be null");
ReactiveElasticsearchOperations elasticsearchOperations) {
this.queryMethod = queryMethod;
this.elasticsearchOperations = elasticsearchOperations;
this.evaluationContextProvider = evaluationContextProvider;
}
/*
@@ -105,8 +95,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
var query = createQuery(parameterAccessor);
Assert.notNull(query, "unsupported query");
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter(),
evaluationContextProvider);
queryMethod.addMethodParameter(query, parameterAccessor, elasticsearchOperations.getElasticsearchConverter());
String indexName = queryMethod.getEntityInformation().getIndexName();
IndexCoordinates index = IndexCoordinates.of(indexName);
@@ -123,7 +112,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
* @param accessor must not be {@literal null}.
* @return
*/
protected abstract BaseQuery createQuery(ElasticsearchParametersParameterAccessor accessor);
protected abstract BaseQuery createQuery(ElasticsearchParameterAccessor accessor);
private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterAccessor accessor,
Converter<Object, Object> resultProcessing) {
@@ -134,8 +123,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
ReactiveElasticsearchOperations operations) {
if (isDeleteQuery()) {
return (query, type, targetType, indexCoordinates) -> operations
.delete(DeleteQuery.builder(query).build(), type, indexCoordinates)
return (query, type, targetType, indexCoordinates) -> operations.delete(query, type, indexCoordinates)
.map(ByQueryResponse::getDeleted);
} else if (isCountQuery()) {
return (query, type, targetType, indexCoordinates) -> operations.count(query, type, indexCoordinates);
@@ -29,7 +29,7 @@ import org.springframework.data.util.TypeInformation;
* @author Peter-Josef Meisch
* @since 5.2
*/
public class ElasticsearchParameter extends Parameter {
class ElasticsearchParameter extends Parameter {
/**
* Creates a new {@link ElasticsearchParameter}.
@@ -15,12 +15,12 @@
*/
package org.springframework.data.elasticsearch.repository.query;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.util.TypeInformation;
/**
@@ -33,13 +33,10 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
private final List<ElasticsearchParameter> scriptedFields = new ArrayList<>();
private final List<ElasticsearchParameter> runtimeFields = new ArrayList<>();
public ElasticsearchParameters(ParametersSource parametersSource) {
public ElasticsearchParameters(Method method, TypeInformation<?> domainType) {
super(parametersSource,
parameter -> new ElasticsearchParameter(parameter, parametersSource.getDomainTypeInformation()));
super(method, parameter -> new ElasticsearchParameter(parameter, domainType));
var domainType = parametersSource.getDomainTypeInformation();
var method = parametersSource.getMethod();
int parameterCount = method.getParameterCount();
for (int i = 0; i < parameterCount; i++) {
MethodParameter methodParameter = new MethodParameter(method, i);
@@ -53,6 +50,7 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
runtimeFields.add(parameter);
}
}
}
private ElasticsearchParameter parameterFactory(MethodParameter methodParameter, TypeInformation<?> domainType) {
@@ -21,7 +21,7 @@ import org.springframework.data.repository.query.ParametersParameterAccessor;
* @author Christoph Strobl
* @since 3.2
*/
public class ElasticsearchParametersParameterAccessor extends ParametersParameterAccessor
class ElasticsearchParametersParameterAccessor extends ParametersParameterAccessor
implements ElasticsearchParameterAccessor {
private final Object[] values;
@@ -18,9 +18,11 @@ package org.springframework.data.elasticsearch.repository.query;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.BaseQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.RuntimeField;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.data.elasticsearch.repository.query.parser.ElasticsearchQueryCreator;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.parser.PartTree;
/**
@@ -32,16 +34,14 @@ import org.springframework.data.repository.query.parser.PartTree;
* @author Mark Paluch
* @author Rasmus Faber-Espensen
* @author Peter-Josef Meisch
* @author Haibo Liu
*/
public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery {
private final PartTree tree;
private final MappingContext<?, ElasticsearchPersistentProperty> mappingContext;
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
super(method, elasticsearchOperations, evaluationContextProvider);
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
super(method, elasticsearchOperations);
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
this.mappingContext = elasticsearchConverter.getMappingContext();
}
@@ -24,7 +24,6 @@ import java.util.List;
import java.util.stream.Stream;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.Query;
@@ -42,16 +41,16 @@ import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.RuntimeField;
import org.springframework.data.elasticsearch.core.query.ScriptedField;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.data.elasticsearch.repository.support.QueryStringProcessor;
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -67,13 +66,12 @@ import org.springframework.util.ClassUtils;
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Alexander Torres
* @author Haibo Liu
*/
public class ElasticsearchQueryMethod extends QueryMethod {
// the following 2 variables exist in the base class, but are private. We need them for
// the following 2 variables exits in the base class, but are private. We need them for
// correct handling of return types (SearchHits), so we have our own values here.
// This means that we have to copy code that initializes these variables and in the
// Alas this means that we have to copy code that initializes these variables and in the
// base class uses them in order to use our variables
protected final Method method;
protected final Class<?> unwrappedReturnType;
@@ -83,6 +81,8 @@ public class ElasticsearchQueryMethod extends QueryMethod {
@Nullable private ElasticsearchEntityMetadata<?> metadata;
@Nullable private final Query queryAnnotation;
@Nullable private final Highlight highlightAnnotation;
private final Lazy<HighlightQuery> highlightQueryLazy = Lazy.of(this::createAnnotatedHighlightQuery);
@Nullable private final SourceFilters sourceFilters;
public ElasticsearchQueryMethod(Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory factory,
@@ -102,16 +102,9 @@ public class ElasticsearchQueryMethod extends QueryMethod {
verifyCountQueryTypes();
}
@SuppressWarnings("removal")
@Override
@Deprecated
protected Parameters<?, ?> createParameters(Method method, TypeInformation<?> domainType) {
return new ElasticsearchParameters(ParametersSource.of(method));
}
@Override
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
return new ElasticsearchParameters(parametersSource);
return new ElasticsearchParameters(method, domainType);
}
protected void verifyCountQueryTypes() {
@@ -150,12 +143,20 @@ public class ElasticsearchQueryMethod extends QueryMethod {
* @throws IllegalArgumentException if no {@link Highlight} annotation is present on the method
* @see #hasAnnotatedHighlight()
*/
public HighlightQuery getAnnotatedHighlightQuery(HighlightConverter highlightConverter) {
public HighlightQuery getAnnotatedHighlightQuery() {
Assert.isTrue(hasAnnotatedHighlight(), "no Highlight annotation present on " + getName());
return highlightQueryLazy.get();
}
private HighlightQuery createAnnotatedHighlightQuery() {
Assert.notNull(highlightAnnotation, "highlightAnnotation must not be null");
return new HighlightQuery(highlightConverter.convert(highlightAnnotation), getDomainClass());
return new HighlightQuery(
org.springframework.data.elasticsearch.core.query.highlight.Highlight.of(highlightAnnotation),
getDomainClass());
}
/**
@@ -295,46 +296,42 @@ public class ElasticsearchQueryMethod extends QueryMethod {
* @param parameterAccessor the accessor with the query method parameter details
* @param converter {@link ElasticsearchConverter} needed to convert entity property names to the Elasticsearch field
* names and for parameter conversion when the includes or excludes are defined as parameters
* @param evaluationContextProvider to provide an evaluation context for SpEL evaluation
* @return source filter with includes and excludes for a query, {@literal null} when no {@link SourceFilters}
* annotation was set on the method.
* @since 5.0
*/
@Nullable
SourceFilter getSourceFilter(ElasticsearchParametersParameterAccessor parameterAccessor,
ElasticsearchConverter converter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
SourceFilter getSourceFilter(ParameterAccessor parameterAccessor, ElasticsearchConverter converter) {
if (sourceFilters == null || (sourceFilters.includes().length == 0 && sourceFilters.excludes().length == 0)) {
return null;
}
ConversionService conversionService = converter.getConversionService();
StringQueryUtil stringQueryUtil = new StringQueryUtil(converter.getConversionService());
FetchSourceFilterBuilder fetchSourceFilterBuilder = new FetchSourceFilterBuilder();
if (sourceFilters.includes().length > 0) {
fetchSourceFilterBuilder.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor,
conversionService, evaluationContextProvider));
fetchSourceFilterBuilder
.withIncludes(mapParameters(sourceFilters.includes(), parameterAccessor, stringQueryUtil));
}
if (sourceFilters.excludes().length > 0) {
fetchSourceFilterBuilder.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor,
conversionService, evaluationContextProvider));
fetchSourceFilterBuilder
.withExcludes(mapParameters(sourceFilters.excludes(), parameterAccessor, stringQueryUtil));
}
return fetchSourceFilterBuilder.build();
}
private String[] mapParameters(String[] source, ElasticsearchParametersParameterAccessor parameterAccessor,
ConversionService conversionService, QueryMethodEvaluationContextProvider evaluationContextProvider) {
private String[] mapParameters(String[] source, ParameterAccessor parameterAccessor,
StringQueryUtil stringQueryUtil) {
List<String> fieldNames = new ArrayList<>();
for (String s : source) {
if (!s.isBlank()) {
String fieldName = new QueryStringProcessor(s, this, conversionService, evaluationContextProvider)
.createQuery(parameterAccessor);
String fieldName = stringQueryUtil.replacePlaceholders(s, parameterAccessor);
// this could be "[\"foo\",\"bar\"]", must be split
if (fieldName.startsWith("[") && fieldName.endsWith("]")) {
// noinspection RegExpRedundantEscape
@@ -355,7 +352,7 @@ public class ElasticsearchQueryMethod extends QueryMethod {
/*
* Copied from the QueryMethod class adding support for collections of SearchHit instances. No static method here.
*/
private Class<?> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
private Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
TypeInformation<?> returnType = metadata.getReturnType(method);
if (!QueryExecutionConverters.supports(returnType.getType())
&& !ReactiveWrapperConverters.supports(returnType.getType())) {
@@ -378,16 +375,13 @@ public class ElasticsearchQueryMethod extends QueryMethod {
}
void addMethodParameter(BaseQuery query, ElasticsearchParametersParameterAccessor parameterAccessor,
ElasticsearchConverter elasticsearchConverter,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
ElasticsearchConverter elasticsearchConverter) {
if (hasAnnotatedHighlight()) {
var highlightQuery = getAnnotatedHighlightQuery(new HighlightConverter(parameterAccessor,
elasticsearchConverter.getConversionService(), evaluationContextProvider, this));
query.setHighlightQuery(highlightQuery);
query.setHighlightQuery(getAnnotatedHighlightQuery());
}
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter, evaluationContextProvider);
var sourceFilter = getSourceFilter(parameterAccessor, elasticsearchConverter);
if (sourceFilter != null) {
query.addSourceFilter(sourceFilter);
}

Some files were not shown because too many files have changed in this diff Show More