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

Compare commits

..

17 Commits

Author SHA1 Message Date
Mark Paluch 2ac1085d03 Release version 4.2.1 (2021.0.1).
See #1775
2021-05-14 12:23:57 +02:00
Mark Paluch e0760e8567 Prepare 4.2.1 (2021.0.1).
See #1775
2021-05-14 12:23:22 +02:00
Mark Paluch f8860c890a Updated changelog.
See #1775
2021-05-14 12:23:19 +02:00
Mark Paluch 3a00ef4375 Updated changelog.
See #1774
2021-05-14 12:06:47 +02:00
Peter-Josef Meisch ad6022f64c SearchPage result in StringQuery methods.
Original Pull Request #1812
Closes #1811

(cherry picked from commit e96d09fa51)
2021-05-13 17:02:57 +02:00
Peter-Josef Meisch da384e5eda update Elasticsearch to 7.12.1
Original Pull Request #1806 
Closes #1805
2021-05-10 18:17:44 +02:00
Peter-Josef Meisch 8ab1a4f098 Refactor DefaultReactiveElasticsearchClient to do request customization with the WebClient. (#1795)
Original Pull Request #1795
Closes #1794

(cherry picked from commit 775bf66401)
2021-04-30 07:31:22 +02:00
Peter-Josef Meisch 40972b21e0 Escape strings with quotes in custom query parameters.
Original Pull Request #1793
Closes #1790

(cherry picked from commit f8fbf7721a)
2021-04-29 06:24:05 +02:00
Peter-Josef Meisch 85af54635d Search with MoreLikeThisQuery should use Pageable.
Original Pull Request #1789
Closes #1787

(cherry picked from commit a2ca312fb2)
2021-04-26 22:32:48 +02:00
Peter-Josef Meisch 105607f6d6 Fix documentation.
Original Pull Request #1786
Closes #1785

(cherry picked from commit 8b7f0f8327)
2021-04-23 17:52:28 +02:00
Greg L. Turnquist d0ee4efd87 Polishing. 2021-04-20 11:05:17 -05:00
Greg L. Turnquist 9c900eca21 Polishing. 2021-04-20 11:00:33 -05:00
Greg L. Turnquist 8cfe165754 Authenticate with artifactory.
See #1750.
2021-04-20 10:41:43 -05:00
Peter-Josef Meisch 30bfee24f0 Custom property names must be used in SourceFilter and source fields.
Original Pull Request #1780
Closes #1778
2021-04-18 14:24:20 +02:00
Peter-Josef Meisch f339fda512 DynamicMapping annotation should be applicable to any object field.
Original Pull Request #1779
Closes #1767
2021-04-17 18:49:18 +02:00
Mark Paluch b2a480df83 After release cleanups.
See #1750
2021-04-14 14:30:41 +02:00
Mark Paluch f765ecac69 Prepare next development iteration.
See #1750
2021-04-14 14:30:38 +02:00
242 changed files with 7262 additions and 13484 deletions
+1 -7
View File
@@ -1,18 +1,12 @@
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
When contributing, please make sure an issue exists in issue tracker and comment on this issue with how you want to address it. By this we not only know that someone is working on an issue, we can also align architectural questions and possible solutions before work is invested . We so can prevent that much work is put into Pull Requests that have little or no chances of being merged.
Make sure that:
-->
- [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc).
- [ ] **There is a ticket in the bug tracker for the project in our [issue tracker](https://github.
com/spring-projects/spring-data-elasticsearch/issues)**. Add the issue number to the _Closes #issue-number_ line below
- [ ] There is a ticket in the bug tracker for the project in our [issue tracker](https://github.com/spring-projects/spring-data-elasticsearch/issues).
- [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Dont submit any formatting related changes.
- [ ] You submit test cases (unit or integration tests) that back your changes.
- [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).
Closes #issue-number
-117
View File
@@ -1,117 +0,0 @@
/*
* Copyright 2007-present 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
Vendored Regular → Executable
BIN
View File
Binary file not shown.
Vendored Regular → Executable
+1 -3
View File
@@ -1,3 +1 @@
#Fri Jun 03 09:42:29 CEST 2022
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
+1 -1
View File
@@ -1,6 +1,6 @@
= Continuous Integration
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=2020.0.0%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=2020.0.0%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F4.0.x&subject=Neumann%20(4.0.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.2.x&subject=Moore%20(3.2.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/]
+1 -1
View File
@@ -1,6 +1,6 @@
= Spring Data contribution guidelines
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here].
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
== Running the test locally
Vendored
+52 -32
View File
@@ -1,15 +1,9 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
}
pipeline {
agent none
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.5.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -18,11 +12,10 @@ pipeline {
}
stages {
stage("test: baseline (main)") {
stage("test: baseline (jdk8)") {
when {
beforeAgent(true)
anyOf {
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
branch '4.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -32,14 +25,14 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
@@ -52,28 +45,28 @@ pipeline {
stage("Test other configurations") {
when {
allOf {
branch 'main'
branch '4.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
parallel {
stage("test: baseline (next)") {
stage("test: baseline (jdk11)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh 'PROFILE=java11 ci/verify.sh'
sh "ci/clean.sh"
}
}
@@ -81,23 +74,23 @@ pipeline {
}
}
stage("test: baseline (LTS)") {
stage("test: baseline (jdk16)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.lts.image']).inside(p['docker.java.inside.docker']) {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh 'PROFILE=java11 ci/verify.sh'
sh "ci/clean.sh"
}
}
@@ -109,9 +102,8 @@ pipeline {
stage('Release to artifactory') {
when {
beforeAgent(true)
anyOf {
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
branch '4.2.x'
not { triggeredBy 'UpstreamCause' }
}
}
@@ -121,13 +113,13 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
@@ -141,6 +133,34 @@ pipeline {
}
}
}
stage('Publish documentation') {
when {
branch '4.2.x'
}
agent {
label 'data'
}
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.distribution-repository=temp-private-local " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
}
}
}
}
post {
+2 -2
View File
@@ -1,6 +1,6 @@
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]]
= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&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]]
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.
@@ -217,7 +217,7 @@ The generated documentation is available from `target/site/reference/html/index.
== Examples
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/main/elasticsearch/example[spring-data-examples] project.
For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/master/elasticsearch/example[spring-data-examples] project.
== License
-29
View File
@@ -1,29 +0,0 @@
# Java versions
java.main.tag=8u332-b09-jdk
java.next.tag=11.0.15_10-jdk
java.lts.tag=17.0.3_7-jdk
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag}
# Supported versions of MongoDB
docker.mongodb.4.0.version=4.0.28
docker.mongodb.4.4.version=4.4.12
docker.mongodb.5.0.version=5.0.6
# Supported versions of Redis
docker.redis.6.version=6.2.6
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.12
# Docker environment settings
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
# Credentials
docker.registry=
docker.credentials=hub.docker.com-springbuildmaster
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
Vendored
+6 -30
View File
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# 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
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
@@ -114,6 +114,7 @@ if $mingw ; then
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
@@ -211,11 +212,7 @@ else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
@@ -224,38 +221,22 @@ else
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
wget "$jarUrl" -O "$wrapperJarPath"
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
curl -o "$wrapperJarPath" "$jarUrl"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
@@ -296,11 +277,6 @@ if $cygwin; then
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
Vendored Regular → Executable
+12 -33
View File
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -26,7 +26,7 @@
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -37,7 +37,7 @@
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -120,44 +120,23 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
echo Found %WRAPPER_JAR%
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
+54 -79
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.3.7</version>
<version>4.2.1</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.6.7</version>
<version>2.5.1</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -18,22 +18,13 @@
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
<properties>
<elasticsearch>7.15.2</elasticsearch>
<log4j>2.17.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.6.7</springdata.commons>
<testcontainers>1.15.3</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<commonslang>2.6</commonslang>
<elasticsearch>7.12.1</elasticsearch>
<log4j>2.13.3</log4j>
<netty>4.1.52.Final</netty>
<springdata.commons>2.5.1</springdata.commons>
<testcontainers>1.15.1</testcontainers>
<java-module-name>spring.data.elasticsearch</java-module-name>
<!--
properties defining the maven phase for the tests and integration tests
set to "none" to disable the corresponding test execution (-Dmvn.unit-test.goal=none)
default execution for unit-test: "test", for the integration tests: "integration-test"
-->
<mvn.unit-test.goal>test</mvn.unit-test.goal>
<mvn.integration-test-elasticsearch.goal>integration-test</mvn.integration-test-elasticsearch.goal>
<mvn.integration-test-opensearch.goal>none</mvn.integration-test-opensearch.goal>
</properties>
<developers>
@@ -140,17 +131,27 @@
<scope>test</scope>
</dependency>
<!-- APACHE -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commonslang}</version>
<scope>test</scope>
</dependency>
<!-- JODA Time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime}</version>
<optional>true</optional>
</dependency>
<!-- Elasticsearch -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -158,12 +159,6 @@
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -261,14 +256,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>${blockhound-junit}</version>
<scope>test</scope>
</dependency>
<!--
we don't use lombok in Spring Data Elasticsearch anymore. But the dependency is set in the parent project, and so the
lombok compiler stuff is executed regardless of the fact that we don't need it.
@@ -285,6 +272,25 @@
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.openwebbeans.test</groupId>
<artifactId>cditest-owb</artifactId>
<version>1.2.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jcdi_1.0_spec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
-->
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
@@ -367,6 +373,9 @@
</resources>
<plugins>
<!--
please do not remove this configuration for surefire - we need that to avoid issue with jar hell
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@@ -385,7 +394,7 @@
<!-- the default-test execution runs only the unit tests -->
<execution>
<id>default-test</id>
<phase>${mvn.unit-test.goal}</phase>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
@@ -393,32 +402,15 @@
<excludedGroups>integration-test</excludedGroups>
</configuration>
</execution>
<!-- execution to run the integration tests against Elasticsearch -->
<!-- execution to run the integration tests -->
<execution>
<id>integration-test-elasticsearch</id>
<phase>${mvn.integration-test-elasticsearch.goal}</phase>
<id>integration-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>elasticsearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration>
</execution>
<!-- execution to run the integration tests against Opensearch -->
<execution>
<id>integration-test-opensearch</id>
<phase>${mvn.integration-test-opensearch.goal}</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>integration-test</groups>
<systemPropertyVariables>
<sde.integration-test.environment>opensearch</sde.integration-test.environment>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
@@ -456,7 +448,9 @@
<profiles>
<profile>
<id>ci</id>
<build>
<plugins>
<plugin>
@@ -473,34 +467,15 @@
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
</excludes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jdk13+</id>
<!-- on jDK13+, Blockhound needs this JVM flag set -->
<activation>
<jdk>[13,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:+AllowRedefinitionToAddDeleteMethods</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
+1 -2
View File
@@ -34,8 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat
[cols="^,^,^,^,^",options="header"]
|===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.15.2 | 5.3.xfootnote:cdv[] | 2.5 .xfootnote:cdv[]
| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x
| 2021.0 (Pascal)footnote:cdv[Currently in development] | 4.2.xfootnote:cdv[] | 7.12.1 | 5.3.xfootnote:cdv[] | 2.4.xfootnote:cdv[]
| 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x
| Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x
| Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x
@@ -3,14 +3,13 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.transport]]
== Transport Client
WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]).
Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used
Elasticsearch <<elasticsearch.versions,version>> but has deprecated the classes using it since version 4.0.
We strongly recommend to use the <<elasticsearch.clients.rest>> instead of the `TransportClient`.
@@ -47,7 +46,6 @@ IndexRequest request = new IndexRequest("spring-data")
IndexResponse response = client.index(request);
----
<.> The `TransportClient` must be configured with the cluster name.
<.> The host and port to connect the client to.
<.> the RefreshPolicy must be set in the `ElasticsearchTemplate` (override `refreshPolicy()` to not use the default)
@@ -56,7 +54,8 @@ IndexResponse response = client.index(request);
[[elasticsearch.clients.rest]]
== High Level REST Client
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns the very same request/response objects and therefore depends on the Elasticsearch core project.
The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns
the very same request/response objects and therefore depends on the Elasticsearch core project.
Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done.
.High Level REST Client
@@ -94,7 +93,6 @@ IndexRequest request = new IndexRequest("spring-data")
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<2> Create the RestHighLevelClient.
<3> It is also possible to obtain the `lowLevelRest()` client.
@@ -133,7 +131,6 @@ Mono<IndexResponse> response = client.index(request ->
.source(singletonMap("feature", "reactive-client"));
);
----
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
====
@@ -165,30 +162,25 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
return headers;
})
.withClientConfigurer( <.>
ReactiveRestClients.WebClientConfigurationCallback.from(webClient -> {
// ...
return webClient;
}))
.withClientConfigurer( <.>
RestClients.RestClientConfigurationCallback.from(clientBuilder -> {
// ...
.withWebClientConfigurer(webClient -> { <.>
//...
return webClient;
})
.withHttpClientConfigurer(clientBuilder -> { <.>
//...
return clientBuilder;
}))
})
. // ... other options
.build();
----
<.> Define default headers, if they need to be customized
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<.> Optionally enable SSL.
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout.
Default is 10 sec.
<.> Set the socket timeout.
Default is 5 sec.
<.> Set the connection timeout. Default is 10 sec.
<.> Set the socket timeout. Default is 5 sec.
<.> Optionally set headers.
<.> Add basic authentication.
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
@@ -196,36 +188,13 @@ Default is 5 sec.
<.> for non-reactive setup a function configuring the REST client
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
If this is used in the reactive setup, the supplier function *must not* block!
=== Elasticsearch 7 compatibility headers
When using Spring Data Elasticsearch 4 - which uses the Elasticsearch 7 client libraries - and accessing an Elasticsearch cluster that is running on version 8, it is necessary to set the compatibility headers
https://www.elastic.co/guide/en/elasticsearch/reference/8.0/rest-api-compatibility.html[see Elasticserach documentation].
This should be done using a header supplier like shown above:
====
[source,java]
----
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
configurationBuilder //
// ...
.withHeaders(() -> {
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
defaultCompatibilityHeaders.add("Accept",
"application/vnd.elasticsearch+json;compatible-with=7");
defaultCompatibilityHeaders.add("Content-Type",
"application/vnd.elasticsearch+json;compatible-with=7");
return defaultCompatibilityHeaders;
});
----
====
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block!
[[elasticsearch.clients.logging]]
== Client Logging
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below.
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs
to be turned on as outlined in the snippet below.
.Enable transport layer logging
[source,xml]
@@ -1,79 +0,0 @@
[[elasticsearch-migration-guide-4.2-4.3]]
= Upgrading from 4.2.x to 4.3.x
This section describes breaking changes from version 4.2.x to 4.3.x and how removed features can be replaced by new introduced features.
[NOTE]
====
Elasticsearch is working on a new Client that will replace the `RestHighLevelClient` because the `RestHighLevelClient` uses code from Elasticsearch core libraries which are not Apache 2 licensed anymore.
Spring Data Elasticsearch is preparing for this change as well.
This means that internally the implementations for the `*Operations` interfaces need to change - which should be no problem if users program against the interfaces like `ElasticsearchOperations` or `ReactiveElasticsearchOperations`.
If you are using the implementation classes like `ElasticsearchRestTemplate` directly, you will need to adapt to these changes.
Spring Data Elasticsearch also removes or replaces the use of classes from the `org.elasticsearch` packages in it's API classes and methods, only using them in the implementation where the access to Elasticsearch is implemented.
For the user that means, that some enum classes that were used are replaced by enums that live in `org.springframework.data.elasticsearch` with the same values, these are internally mapped onto the Elasticsearch ones.
Places where classes are used that cannot easily be replaced, this usage is marked as deprecated, we are working on replacements.
Check the sections on <<elasticsearch-migration-guide-4.2-4.3.deprecations>> and <<elasticsearch-migration-guide-4.2-4.3.breaking-changes>> for further details.
====
[[elasticsearch-migration-guide-4.2-4.3.deprecations]]
== Deprecations
=== suggest methods
In `SearchOperations`, and so in `ElasticsearchOperations` as well, the `suggest` methods taking a `org.elasticsearch.search.suggest.SuggestBuilder` as argument and returning a `org.elasticsearch.action.search.SearchResponse` have been deprecated.
Use `SearchHits<T> search(Query query, Class<T> clazz)` instead, passing in a `NativeSearchQuery` which can contain a `SuggestBuilder` and read the suggest results from the returned `SearchHit<T>`.
In `ReactiveSearchOperations` the new `suggest` methods return a `Mono<org.springframework.data.elasticsearch.core.suggest.response.Suggest>` now.
Here as well the old methods are deprecated.
[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]]
== Breaking Changes
=== Removal of `org.elasticsearch` classes from the API.
* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.annotations.Document` annotation the `versionType()` property has changed to `org.springframework.data.elasticsearch.annotations.Document.VersionType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the `searchType()` property has changed to `org.springframework.data.elasticsearch.core.query.Query.SearchType`, the available enum values are the same.
* In the `org.springframework.data.elasticsearch.core.query.Query` interface the return value of `timeout()` was changed to `java.time.Duration`.
* The `SearchHits<T>`class does not contain the `org.elasticsearch.search.aggregations.Aggregations` anymore.
Instead it now contains an instance of the `org.springframework.data.elasticsearch.core.AggregationsContainer<T>` class where `T` is the concrete aggregations type from the underlying client that is used.
Currently this will be a `org
.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations` object; later different implementations will be available.
The same change has been done to the `ReactiveSearchOperations.aggregate()` functions, the now return a `Flux<AggregationContainer<?>>`.
Programs using the aggregations need to be changed to cast the returned value to the appropriate class to further proces it.
* methods that might have thrown a `org.elasticsearch.ElasticsearchStatusException` now will throw `org.springframework.data.elasticsearch.RestStatusException` instead.
=== Handling of field and sourceFilter properties of Query
Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`.
This was not correct, as these are different things for Elasticsearch.
This has been corrected.
As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`.
=== search_type default value
The default value for the `search_type` in Elasticsearch is `query_then_fetch`.
This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`.
=== BulkOptions changes
Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type:
* the type of the `timeout` property has been changed to `java.time.Duration`.
* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`.
=== IndicesOptions change
Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`.
=== Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
=== Other renamings
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
Likewise the implementations classes named _XXPersistentPropertyConverter_ have been renamed to _XXPropertyValueConverter_.
@@ -7,8 +7,7 @@ It is recommended to add those operations as custom implementation as described
[[elasticsearc.misc.index.settings]]
== Index settings
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation.
The following arguments are available:
When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available:
* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them.
* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath
@@ -43,38 +42,10 @@ class Entity {
// getter and setter...
}
----
<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_)
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
====
[[elasticsearch.misc.mappings]]
== Index Mapping
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation.
In addition to that it is possible to add the `@Mapping` annotation to a class.
This annotation has the following properties:
* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done.
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
[[elasticsearch.misc.filter]]
== Filter Builder
@@ -170,10 +141,7 @@ interface SampleEntityRepository extends Repository<SampleEntity, String> {
[[elasticsearch.misc.sorts]]
== Sort options
In addition to the default sort options described in <<repositories.paging-and-sorting>>, Spring Data Elasticsearch provides the class `org.springframework.data.elasticsearch.core.query.Order` which derives from `org.springframework.data.domain.Sort.Order`.
It offers additional parameters that can be sent to Elasticsearch when specifying the sorting of the result (see https://www.elastic.co/guide/en/elasticsearch/reference/7.15/sort-search-results.html).
There also is the `org.springframework.data.elasticsearch.core.query.GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
In addition to the default sort options described <<repositories.paging-and-sorting>> Spring Data Elasticsearch has a `GeoDistanceOrder` class which can be used to have the result of a search operation ordered by geographical distance.
If the class to be retrieved has a `GeoPoint` property named _location_, the following `Sort` would sort the results by distance to the given point:
@@ -183,81 +151,3 @@ If the class to be retrieved has a `GeoPoint` property named _location_, the fol
Sort.by(new GeoDistanceOrder("location", new GeoPoint(48.137154, 11.5761247)))
----
====
[[elasticsearch.misc.runtime-fields]]
== Runtime Fields
From version 7.12 on Elasticsearch has added the feature of runtime fields (https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime.html).
Spring Data Elasticsearch supports this in two ways:
=== Runtime field definitions in the index mappings
The first way to define runtime fields is by adding the definitions to the index mappings (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html).
To use this approach in Spring Data Elasticsearch the user must provide a JSON file that contains the corresponding definition, for example:
.runtime-fields.json
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
The path to this JSON file, which must be present on the classpath, must then be set in the `@Mapping` annotation of the entity:
====
[source,java]
----
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/runtime-fields.json")
public class RuntimeFieldEntity {
// properties, getter, setter,...
}
----
====
=== Runtime fields definitions set on a Query
The second way to define runtime fields is by adding the definitions to a search query (see https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-search-request.html).
The following code example shows how to do this with Spring Data Elasticsearch :
The entity used is a simple object that has a `price` property:
====
[source,java]
----
@Document(indexName = "some_index_name")
public class SomethingToBuy {
private @Id @Nullable String id;
@Nullable @Field(type = FieldType.Text) private String description;
@Nullable @Field(type = FieldType.Double) private Double price;
// getter and setter
}
----
====
The following query uses a runtime field that calculates a `priceWithTax` value by adding 19% to the price and uses this value in the search query to find all entities where `priceWithTax` is higher or equal than a given value:
====
[source,java]
----
RuntimeField runtimeField = new RuntimeField("priceWithTax", "double", "emit(doc['price'].value * 1.19)");
Query query = new CriteriaQuery(new Criteria("priceWithTax").greaterThanEqual(16.5));
query.addRuntimeField(runtimeField);
SearchHits<SomethingToBuy> searchHits = operations.search(query, SomethingToBuy.class);
----
====
This works with every implementation of the `Query` interface.
@@ -1,16 +1,6 @@
[[new-features]]
= What's new
[[new-features.4-3-0]]
== New in Spring Data Elasticsearch 4.3
* Upgrade to Elasticsearch 7.15.2.
* Allow runtime_fields to be defined in the index mapping.
* Add native support for range field types by using a range object.
* Add repository search for nullable or empty properties.
* Enable custom converters for single fields.
* Supply a custom `Sort.Order` providing Elasticsearch specific parameters.
[[new-features.4-2-0]]
== New in Spring Data Elasticsearch 4.2
@@ -34,6 +34,8 @@ The following annotations are available:
The most important attributes are:
** `indexName`: the name of the index to store this entity in.
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
** `type`: [line-through]#the mapping type.
If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0)
** `createIndex`: flag whether to create an index on repository bootstrapping.
Default value is _true_.
See <<elasticsearch.repositories.autocreation>>
@@ -47,37 +49,34 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu
* `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference):
** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used.
** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_.
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types].
If the field type is not specified, it defaults to `FieldType.Auto`.
This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored (check the Elasticsearch documentation for dynamic mapping rules).
See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]
** `format`: One or more built-in date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
** `pattern`: One or more custom date formats, see the next section <<elasticsearch.mapping.meta-model.date-formats>>.
** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_.
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
* `@GeoPoint`: Marks a field as _geo_point_ datatype.
Can be omitted if the field is an instance of the `GeoPoint` class.
* `@ValueConverter` defines a class to be used to convert the given property.
In difference to a registered Spring `Converter` this only converts the annotated property and not every property of the given type.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
[[elasticsearch.mapping.meta-model.date-formats]]
==== Date format mapping
Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type.
This paragraph describes the use of
Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation
of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of
`FieldType.Date`.
There are two attributes of the `@Field` annotation that define which date format information is written to the mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats])
There are two attributes of the `@Field` annotation that define which date format information is written to the
mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats])
The `format` attributes is used to define at least one of the predefined formats.
If it is not defined, then a default value of __date_optional_time_ and _epoch_millis_ is used.
The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a
default value of __date_optional_time_ and _epoch_millis_ is used.
The `pattern` attribute can be used to add additional custom format strings.
If you want to use only custom date formats, you must set the `format` property to empty `{}`.
The `pattern` attribute can be used to add additional custom format strings. If you want to use only custom date formats, you must set the `format` property to empty `{}`.
The following table shows the different attributes and the mapping created from their values:
[cols=2*,options=header]
|===
| annotation
@@ -103,59 +102,12 @@ The following table shows the different attributes and the mapping created from
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
==== Range types
When a field is annotated with a type of one of _Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,_ or _Ip_Range_ the field must be an instance of a class that will be mapped to an Elasticsearch range, for example:
====
[source,java]
----
class SomePersonData {
@Field(type = FieldType.Integer_Range)
private ValidAge validAge;
// getter and setter
}
class ValidAge {
@Field(name="gte")
private Integer from;
@Field(name="lte")
private Integer to;
// getter and setter
}
----
====
As an alternative Spring Data Elasticsearch provides a `Range<T>` class so that the previous example can be written as:
====
[source,java]
----
class SomePersonData {
@Field(type = FieldType.Integer_Range)
private Range<Integer> validAge;
// getter and setter
}
----
====
Supported classes for the type `<T>` are `Integer`, `Long`, `Float`, `Double`, `Date` and classes that implement the
`TemporalAccessor` interface.
==== Mapped field names
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch.
This can be changed for individual field by using the `@Field` annotation on that property.
Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch. This can be changed for individual field by using the `@Field` annotation on that property.
It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<<elasticsearch.clients>>).
If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch.
A `FieldNamingStrategy` applies to all entities; it can be overwritten by setting a specific name with `@Field` on a property.
It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<<elasticsearch.clients>>). If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch. A `FieldNamingStrategy` applies to all entities; it can be overwritten by
setting a specific name with `@Field` on a property.
[[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules
@@ -186,7 +138,6 @@ public class Person { <1>
"lastname" : "Connor"
}
----
<1> By default the domain types class name is used for the type hint.
====
@@ -214,32 +165,11 @@ public class Person {
"id" : ...
}
----
<1> The configured alias is used when writing the entity.
====
NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration.
===== Disabling Type Hints
It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict.
In this case, writing the type hint will produce an error, as the field cannot be added automatically.
Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <<elasticsearch.clients>>).
As an alternativ they can be disabled for a single index with the `@Document` annotation:
====
[source,java]
----
@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)
----
====
WARNING: We strongly advise against disabling Type Hints.
Only do this if you are forced to.
Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely.
==== Geospatial Types
Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
@@ -425,7 +355,6 @@ public class Config extends AbstractElasticsearchConfiguration {
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
----
<1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result.
@@ -20,11 +20,11 @@ The default implementations of the interfaces offer:
[NOTE]
====
.Index management and automatic creation of indices and mappings.
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`.
It is the user's responsibility to call the methods.
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster. Details of the index that will be created
can be set by using the `@Setting` annotation, refer to <<elasticsearc.misc.index.settings>> for further information.
**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`. It is the user's responsibility to call the methods.
There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see <<elasticsearch.repositories.autocreation>>
@@ -58,7 +58,6 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
}
}
----
<1> Setting up the <<elasticsearch.clients.transport>>.
Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
@@ -83,7 +82,6 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
// no special bean creation needed <2>
}
----
<1> Setting up the <<elasticsearch.clients.rest>>.
<2> The base class `AbstractElasticsearchConfiguration` already provides the `elasticsearchTemplate` bean.
====
@@ -129,7 +127,6 @@ public class TestController {
}
----
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster.
<3> Retrieve the entity with a query by id.
@@ -167,7 +164,6 @@ Contains the following information:
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
* Returned suggest results
.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.
@@ -186,12 +182,12 @@ Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchO
[[elasticsearch.operations.criteriaquery]]
=== CriteriaQuery
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries.
They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries. They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**
`Criteria` and their usage are best explained by example (let's assume we have a `Book` entity with a `price` property):
`Criteria` and their usage are best explained by example
(let's assume we have a `Book` entity with a `price` property):
.Get books with a given price
====
@@ -215,7 +211,7 @@ Query query = new CriteriaQuery(criteria);
When chaining `Criteria`, by default a AND logic is used:
.Get all persons with first name _James_ and last name _Miller_:
.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
@@ -223,13 +219,11 @@ Criteria criteria = new Criteria("lastname").is("Miller") <1>
.and("firstname").is("James") <2>
Query query = new CriteriaQuery(criteria);
----
<1> the first `Criteria`
<2> the and() creates a new `Criteria` and chaines it to the first one.
====
If you want to create nested queries, you need to use subqueries for this.
Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:
If you want to create nested queries, you need to use subqueries for this. Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:
.Nested subqueries
====
@@ -242,7 +236,6 @@ Criteria miller = new Criteria("lastName").is("Miller") <.>
);
Query query = new CriteriaQuery(criteria);
----
<.> create a first `Criteria` for the last name
<.> this is combined with AND to a subCriteria
<.> This sub Criteria is an OR combination for the first name _John_
@@ -288,3 +281,5 @@ Query query = new NativeSearchQueryBuilder()
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
@@ -242,6 +242,10 @@ A list of supported keywords for Elasticsearch is shown below.
| `findByNameNotIn(Collection<String>names)`
| `{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}`
| `Near`
| `findByStoreNear`
| `Not Supported Yet !`
| `True`
| `findByAvailableTrue`
| `{ "query" : {
@@ -273,26 +277,6 @@ A list of supported keywords for Elasticsearch is shown below.
}, "sort":[{"name":{"order":"desc"}}]
}`
| `Exists`
| `findByNameExists`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsNull`
| `findByNameIsNull`
| `{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}`
| `IsNotNull`
| `findByNameIsNotNull`
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
| `IsEmpty`
| `findByNameIsEmpty`
| `{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}`
| `IsNotEmpty`
| `findByNameIsNotEmpty`
| `{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}`
|===
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
@@ -8,6 +8,4 @@ include::elasticsearch-migration-guide-3.2-4.0.adoc[]
include::elasticsearch-migration-guide-4.0-4.1.adoc[]
include::elasticsearch-migration-guide-4.1-4.2.adoc[]
include::elasticsearch-migration-guide-4.2-4.3.adoc[]
:leveloffset: -1
@@ -1,49 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch;
import org.springframework.dao.DataAccessException;
/**
* Exception class for REST status exceptions independent from the used client/backend.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class RestStatusException extends DataAccessException {
// we do not use a dedicated status class from Elasticsearch, OpenSearch, Spring web or webflux here
private final int status;
public RestStatusException(int status, String msg) {
super(msg);
this.status = status;
}
public RestStatusException(int status, String msg, Throwable cause) {
super(msg, cause);
this.status = status;
}
public int getStatus() {
return status;
}
@Override
public String toString() {
return "RestStatusException{" + "status=" + status + "} " + super.toString();
}
}
@@ -1,18 +1,3 @@
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
@@ -22,11 +7,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
/**
* Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
*
* @author Robert Gruendler
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@@ -36,26 +22,9 @@ public @interface CompletionContext {
String name();
ContextMappingType type();
ContextMapping.Type type();
String precision() default "";
String path() default "";
/**
* @since 4.3
*/
enum ContextMappingType {
CATEGORY("category"), GEO("geo");
private final String mappedName;
ContextMappingType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
}
@@ -23,15 +23,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Based on the reference doc -
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
* Based on the reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
*
* @author Mewes Kochheim
* @author Robert Gruendler
* @author Peter-Josef Meisch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface CompletionField {
@@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.elasticsearch.index.VersionType;
import org.springframework.data.annotation.Persistent;
/**
@@ -32,7 +33,6 @@ import org.springframework.data.annotation.Persistent;
* @author Ivan Greene
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Sascha Woo
*/
@Persistent
@Inherited
@@ -44,7 +44,7 @@ public @interface Document {
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li>Cannot include \, /, *, ?, ", &gt;, &lt;, |, ` ` (space character), ,, #</li>
* <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
@@ -105,25 +105,4 @@ public @interface Document {
* Configuration of version management.
*/
VersionType versionType() default VersionType.EXTERNAL;
/**
* Defines if type hints should be written. {@see WriteTypeHint}.
*
* @since 4.3
*/
WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT;
/**
* Controls how Elasticsearch dynamically adds fields to the document.
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* @since 4.3
*/
enum VersionType {
INTERNAL, EXTERNAL, EXTERNAL_GTE
}
}
@@ -1,60 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
/**
* Values for the {@code dynamic} mapping parameter.
*
* @author Sascha Woo
* @since 4.3
*/
public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE("true"),
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME("runtime"),
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE("false"),
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT("strict"),
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT("nherit");
private final String mappedName;
Dynamic(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -26,14 +26,11 @@ import java.lang.annotation.Target;
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
@Deprecated
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;
@@ -17,23 +17,10 @@ package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
True, False, Strict
}
@@ -9,17 +9,19 @@ import java.lang.annotation.Target;
import org.springframework.data.annotation.Persistent;
/**
* Elasticsearch dynamic templates mapping. This annotation is handy if you prefer apply dynamic templates on fields
* with annotation e.g. {@link Field} with type = FieldType.Object etc. instead of static mapping on Document via
* {@link Mapping} annotation. DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
* Elasticsearch dynamic templates mapping.
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
*
* @author Petr Kukral
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Target({ElementType.TYPE})
public @interface DynamicTemplates {
String mappingPath() default "";
}
@@ -195,20 +195,4 @@ public @interface Field {
* @since 4.2
*/
int dims() default -1;
/**
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
/**
* marks this field to be excluded from the _source in Elasticsearch
* (https://www.elastic.co/guide/en/elasticsearch/reference/7.15/mapping-source-field.html#include-exclude)
*
* @since 4.3
*/
boolean excludeFromSource() default false;
}
@@ -26,51 +26,40 @@ package org.springframework.data.elasticsearch.annotations;
* @author Morgan Lutz
*/
public enum FieldType {
Auto("auto"), //
Text("text"), //
Keyword("keyword"), //
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Half_Float("half_float"), //
Scaled_Float("scaled_float"), //
Date("date"), //
Date_Nanos("date_nanos"), //
Boolean("boolean"), //
Binary("binary"), //
Integer_Range("integer_range"), //
Float_Range("float_range"), //
Long_Range("long_range"), //
Double_Range("double_range"), //
Date_Range("date_range"), //
Ip_Range("ip_range"), //
Object("object"), //
Nested("nested"), //
Ip("ip"), //
TokenCount("token_count"), //
Percolator("percolator"), //
Flattened("flattened"), //
Search_As_You_Type("search_as_you_type"), //
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type, //
/** @since 4.1 */
Rank_Feature("rank_feature"), //
Rank_Feature, //
/** @since 4.1 */
Rank_Features("rank_features"), //
Rank_Features, //
/** since 4.2 */
Wildcard("wildcard"), //
Wildcard, //
/** @since 4.2 */
Dense_Vector("dense_vector") //
;
private final String mappedName;
FieldType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
Dense_Vector //
}
@@ -27,7 +27,6 @@ import org.springframework.data.annotation.Persistent;
* Elasticsearch Mapping
*
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@@ -39,42 +38,8 @@ public @interface Mapping {
/**
* whether mappings are enabled
*
*
* @since 4.2
*/
boolean enabled() default true;
/**
* whether date_detection is enabled
*
* @since 4.3
*/
Detection dateDetection() default Detection.DEFAULT;
/**
* whether numeric_detection is enabled
*
* @since 4.3
*/
Detection numericDetection() default Detection.DEFAULT;
/**
* custom dynamic date formats
*
* @since 4.3
*/
String[] dynamicDateFormats() default {};
/**
* classpath to a JSON file containing the values for a runtime mapping definition. The file must contain the JSON
* object that is written as the value of the runtime property. {@see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html">elasticsearch doc</a>}
*
* @since 4.3
*/
String runtimeFieldsPath() default "";
enum Detection {
DEFAULT, TRUE, FALSE;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2014-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,26 +15,22 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.mapping.context.MappingContext;
import java.lang.annotation.*;
import org.springframework.data.annotation.Persistent;
/**
* Defines if type hints should be written. Used by {@link Document} annotation.
* Parent
*
* @author Peter-Josef Meisch
* @since 4.3
* @author Philipp Jardas
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
public enum WriteTypeHint {
@Deprecated
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Parent {
/**
* Use the global settings from the {@link MappingContext}.
*/
DEFAULT,
/**
* Always write type hints for the entity.
*/
TRUE,
/**
* Never write type hints for the entity.
*/
FALSE
String type();
}
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.annotations;
import org.springframework.data.annotation.QueryAnnotation;
import java.lang.annotation.*;
/**
@@ -24,13 +23,11 @@ import java.lang.annotation.*;
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Steven Pearce
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Documented
@QueryAnnotation
public @interface Query {
/**
@@ -1,48 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
/**
* Annotation to put on a property of an entity to define a value converter which can convert the property to a type
* that Elasticsearch understands and back.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface ValueConverter {
/**
* Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must
* provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it
* must only have one enum value.
*
* @return the class to use for conversion
*/
Class<? extends PropertyValueConverter> value();
}
@@ -27,7 +27,6 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
@@ -121,16 +120,16 @@ public interface ClientConfiguration {
boolean useSsl();
/**
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
* Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
*
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured.
* @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured.
*/
Optional<SSLContext> getSslContext();
/**
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
*
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
* @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured.
*/
Optional<HostnameVerifier> getHostNameVerifier();
@@ -153,7 +152,7 @@ public interface ClientConfiguration {
/**
* Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy.
*
*
* @return the path prefix.
* @since 4.0
*/
@@ -162,7 +161,7 @@ public interface ClientConfiguration {
/**
* returns an optionally set proxy in the form host:port
*
*
* @return the optional proxy
* @since 4.0
*/
@@ -174,19 +173,11 @@ public interface ClientConfiguration {
Function<WebClient, WebClient> getWebClientConfigurer();
/**
* @return the Rest Client configuration callback.
* @return the client configuration callback.
* @since 4.2
* @deprecated since 4.3 use {@link #getClientConfigurers()}
*/
@Deprecated
HttpClientConfigCallback getHttpClientConfigurer();
/**
* @return the client configuration callbacks
* @since 4.3
*/
<T> List<ClientConfigurationCallback<?>> getClientConfigurers();
/**
* @return the supplier for custom headers.
*/
@@ -283,7 +274,7 @@ public interface ClientConfiguration {
TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders);
/**
* Configure the {@literal milliseconds} for the connect-timeout.
* Configure the {@literal milliseconds} for the connect timeout.
*
* @param millis the timeout to use.
* @return the {@link TerminalClientConfigurationBuilder}
@@ -336,7 +327,7 @@ public interface ClientConfiguration {
/**
* Configure the path prefix that will be prepended to any HTTP(s) requests
*
*
* @param pathPrefix the pathPrefix.
* @return the {@link TerminalClientConfigurationBuilder}
* @since 4.0
@@ -351,36 +342,21 @@ public interface ClientConfiguration {
/**
* set customization hook in case of a reactive configuration
*
*
* @param webClientConfigurer function to configure the WebClient
* @return the {@link TerminalClientConfigurationBuilder}.
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link ReactiveRestClients.WebClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
/**
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
*
*
* @param httpClientConfigurer configuration callback, must not be null.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.2
* @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with
* {@link RestClients.RestClientConfigurationCallback}
*/
@Deprecated
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
/**
* Register a {@link ClientConfigurationCallback} to configure the client.
*
* @param clientConfigurer configuration callback, must not be {@literal null}.
* @return the {@link TerminalClientConfigurationBuilder}.
* @since 4.3
*/
TerminalClientConfigurationBuilder withClientConfigurer(ClientConfigurationCallback<?> clientConfigurer);
/**
* set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers
* that should be sent with the request. A common use case is passing in authentication headers that may change.
@@ -401,15 +377,4 @@ public interface ClientConfiguration {
*/
ClientConfiguration build();
}
/**
* Callback to be executed to configure a client.
*
* @param <T> the type of the client configuration class.
* @since 4.3
*/
@FunctionalInterface
interface ClientConfigurationCallback<T> {
T configure(T clientConfigurer);
}
}
@@ -31,7 +31,6 @@ import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -50,7 +49,7 @@ import org.springframework.web.reactive.function.client.WebClient;
class ClientConfigurationBuilder
implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder {
private final List<InetSocketAddress> hosts = new ArrayList<>();
private List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = HttpHeaders.EMPTY;
private boolean useSsl;
private @Nullable SSLContext sslContext;
@@ -63,8 +62,7 @@ class ClientConfigurationBuilder
private @Nullable String proxy;
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
@Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
/*
* (non-Javadoc)
@@ -208,7 +206,6 @@ class ClientConfigurationBuilder
Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null");
this.webClientConfigurer = webClientConfigurer;
this.clientConfigurers.add(ReactiveRestClients.WebClientConfigurationCallback.from(webClientConfigurer));
return this;
}
@@ -218,18 +215,6 @@ class ClientConfigurationBuilder
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers
.add(RestClients.RestClientConfigurationCallback.from(httpClientConfigurer::customizeHttpClient));
return this;
}
@Override
public TerminalClientConfigurationBuilder withClientConfigurer(
ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer) {
Assert.notNull(clientConfigurer, "clientConfigurer must not be null");
this.clientConfigurers.add(clientConfigurer);
return this;
}
@@ -257,7 +242,7 @@ class ClientConfigurationBuilder
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, clientConfigurers, headersSupplier);
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {
@@ -32,10 +32,7 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
* @since 3.1
* @deprecated only used in {@link TransportClientFactoryBean}.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
class ClusterNodes implements Streamable<TransportAddress> {
public static ClusterNodes DEFAULT = ClusterNodes.of("127.0.0.1:9300");
@@ -47,7 +44,7 @@ class ClusterNodes implements Streamable<TransportAddress> {
/**
* Creates a new {@link ClusterNodes} by parsing the given source.
*
*
* @param source must not be {@literal null} or empty.
*/
private ClusterNodes(String source) {
@@ -77,14 +74,15 @@ class ClusterNodes implements Streamable<TransportAddress> {
/**
* Creates a new {@link ClusterNodes} by parsing the given source. The expected format is a comma separated list of
* host-port-combinations separated by a colon: {@code host:port,host:port,…}.
*
*
* @param source must not be {@literal null} or empty.
* @return
*/
public static ClusterNodes of(String source) {
return new ClusterNodes(source);
}
/*
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@@ -55,13 +55,12 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final Function<WebClient, WebClient> webClientConfigurer;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
private final List<ClientConfigurationCallback<?>> clientConfigurers;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
List<ClientConfigurationCallback<?>> clientConfigurers, Supplier<HttpHeaders> headersSupplier) {
Supplier<HttpHeaders> headersSupplier) {
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
this.headers = new HttpHeaders(headers);
@@ -74,7 +73,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.proxy = proxy;
this.webClientConfigurer = webClientConfigurer;
this.httpClientConfigurer = httpClientConfigurer;
this.clientConfigurers = clientConfigurers;
this.headersSupplier = headersSupplier;
}
@@ -134,12 +132,6 @@ class DefaultClientConfiguration implements ClientConfiguration {
return httpClientConfigurer;
}
@SuppressWarnings("unchecked")
@Override
public <T> List<ClientConfigurationCallback<?>> getClientConfigurers() {
return clientConfigurers;
}
@Override
public Supplier<HttpHeaders> getHeadersSupplier() {
return headersSupplier;
@@ -0,0 +1,173 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.logging.LogConfigurator;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.InternalSettingsPreparer;
import org.elasticsearch.node.Node;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.transport.Netty4Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* NodeClientFactoryBean
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Ilkang Na
* @author Peter-Josef Meisch
* @deprecated since 4.1, we're not supporting embedded Node clients anymore, use the REST client
*/
@Deprecated
public class NodeClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class);
private boolean local;
private boolean enableHttp;
private @Nullable String clusterName;
private @Nullable Node node;
private @Nullable NodeClient nodeClient;
private @Nullable String pathData;
private @Nullable String pathHome;
private @Nullable String pathConfiguration;
public static class TestNode extends Node {
private static final String DEFAULT_NODE_NAME = "spring-data-elasticsearch-nodeclientfactorybean-test";
public TestNode(Settings preparedSettings, Collection<Class<? extends Plugin>> classpathPlugins) {
super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null,
() -> DEFAULT_NODE_NAME), classpathPlugins, false);
}
protected void registerDerivedNodeNameWithLogger(String nodeName) {
try {
LogConfigurator.setNodeName(nodeName);
} catch (Exception e) {
// nagh - just forget about it
}
}
}
NodeClientFactoryBean() {}
public NodeClientFactoryBean(boolean local) {
this.local = local;
}
@Override
public NodeClient getObject() {
if (nodeClient == null) {
throw new FactoryBeanNotInitializedException();
}
return nodeClient;
}
@Override
public Class<? extends Client> getObjectType() {
return NodeClient.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
Settings settings = Settings.builder() //
.put(loadConfig()) //
.put("transport.type", "netty4") //
.put("http.type", "netty4") //
.put("path.home", this.pathHome) //
.put("path.data", this.pathData) //
.put("cluster.name", this.clusterName) //
.put("node.max_local_storage_nodes", 100) //
.build();
node = new TestNode(settings, Collections.singletonList(Netty4Plugin.class));
nodeClient = (NodeClient) node.start().client();
}
private Settings loadConfig() throws IOException {
if (!StringUtils.isEmpty(pathConfiguration)) {
InputStream stream = getClass().getClassLoader().getResourceAsStream(pathConfiguration);
if (stream != null) {
return Settings.builder().loadFromStream(pathConfiguration,
getClass().getClassLoader().getResourceAsStream(pathConfiguration), false).build();
}
logger.error(String.format("Unable to read node configuration from file [%s]", pathConfiguration));
}
return Settings.builder().build();
}
public void setLocal(boolean local) {
this.local = local;
}
public void setEnableHttp(boolean enableHttp) {
this.enableHttp = enableHttp;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public void setPathData(String pathData) {
this.pathData = pathData;
}
public void setPathHome(String pathHome) {
this.pathHome = pathHome;
}
public void setPathConfiguration(String configuration) {
this.pathConfiguration = configuration;
}
@Override
public void destroy() {
try {
// NodeClient.close() is a noop, no need to call it here
nodeClient = null;
logger.info("Closing elasticSearch node");
if (node != null) {
node.close();
node = null;
}
} catch (final Exception e) {
logger.error("Error closing ElasticSearch client: ", e);
}
}
}
@@ -22,7 +22,6 @@ import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -37,7 +36,6 @@ import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;
import org.elasticsearch.client.RestClient;
@@ -56,7 +54,6 @@ import org.springframework.util.Assert;
* @author Huw Ayling-Miller
* @author Henrique Amaral
* @author Peter-Josef Meisch
* @author Nic Hines
* @since 3.2
*/
public final class RestClients {
@@ -107,27 +104,22 @@ public final class RestClients {
Duration connectTimeout = clientConfiguration.getConnectTimeout();
if (!connectTimeout.isNegative()) {
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
}
Duration socketTimeout = clientConfiguration.getSocketTimeout();
Duration timeout = clientConfiguration.getSocketTimeout();
if (!socketTimeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
if (!timeout.isNegative()) {
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
}
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof RestClientConfigurationCallback) {
RestClientConfigurationCallback restClientConfigurationCallback = (RestClientConfigurationCallback) clientConfigurer;
clientBuilder = restClientConfigurationCallback.configure(clientBuilder);
}
}
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
return clientBuilder;
});
@@ -206,7 +198,7 @@ public final class RestClients {
}
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "",
buffer::toString);
() -> new String(buffer.toByteArray()));
} else {
ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "");
}
@@ -221,7 +213,7 @@ public final class RestClients {
/**
* Interceptor to inject custom supplied headers.
*
*
* @since 4.0
*/
private static class CustomHeaderInjector implements HttpRequestInterceptor {
@@ -241,23 +233,4 @@ public final class RestClients {
}
}
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the RestClient with a {@link HttpAsyncClientBuilder}
*
* @since 4.3
*/
public interface RestClientConfigurationCallback
extends ClientConfiguration.ClientConfigurationCallback<HttpAsyncClientBuilder> {
static RestClientConfigurationCallback from(
Function<HttpAsyncClientBuilder, HttpAsyncClientBuilder> clientBuilderCallback) {
Assert.notNull(clientBuilderCallback, "clientBuilderCallback must not be null");
// noinspection NullableProblems
return clientBuilderCallback::apply;
}
}
}
@@ -83,11 +83,11 @@ import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
@@ -101,7 +101,6 @@ import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.reactivestreams.Publisher;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.ClientLogger;
@@ -112,7 +111,6 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices;
import org.springframework.data.elasticsearch.client.util.NamedXContents;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.data.elasticsearch.core.ResponseConverter;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpHeaders;
@@ -289,21 +287,9 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
provider = provider.withPathPrefix(clientConfiguration.getPathPrefix());
}
Function<WebClient, WebClient> webClientConfigurer = webClient -> {
for (ClientConfiguration.ClientConfigurationCallback<?> clientConfigurer : clientConfiguration
.getClientConfigurers()) {
if (clientConfigurer instanceof ReactiveRestClients.WebClientConfigurationCallback) {
ReactiveRestClients.WebClientConfigurationCallback webClientConfigurationCallback = (ReactiveRestClients.WebClientConfigurationCallback) clientConfigurer;
webClient = webClientConfigurationCallback.configure(webClient);
}
}
return webClient;
};
provider = provider //
.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) //
.withWebClientConfigurer(webClientConfigurer) //
.withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) //
.withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> {
HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get();
@@ -499,7 +485,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
public Mono<ByQueryResponse> updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) {
return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) //
.next() //
.map(ResponseConverter::byQueryResponseOf);
.map(ByQueryResponse::of);
}
@Override
@@ -837,7 +823,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
return Mono.error(BytesRestResponse.errorFromXContent(createParser(mediaType, content)));
} catch (Exception e) {
return Mono.error(new RestStatusException(response.statusCode().value(), content));
return Mono
.error(new ElasticsearchStatusException(content, RestStatus.fromCode(response.statusCode().value())));
}
}
}
@@ -870,14 +857,14 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType());
return response.body(BodyExtractors.toMono(byte[].class)) //
.switchIfEmpty(Mono.error(new RestStatusException(status.getStatus(),
String.format("%s request to %s returned error code %s and no body.", request.getMethod(),
request.getEndpoint(), statusCode))))
.switchIfEmpty(Mono.error(
new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.",
request.getMethod(), request.getEndpoint(), statusCode), status)))
.map(bytes -> new String(bytes, StandardCharsets.UTF_8)) //
.flatMap(content -> contentOrError(content, mediaType, status))
.flatMap(unused -> Mono
.error(new RestStatusException(status.getStatus(), String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode))));
.error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s.",
request.getMethod(), request.getEndpoint(), statusCode), status)));
}
private <T> Publisher<? extends T> handleClientError(String logId, ClientResponse response, Class<T> responseType) {
@@ -909,7 +896,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
if (exception != null) {
StringBuilder sb = new StringBuilder();
buildExceptionMessages(sb, exception);
return Mono.error(new RestStatusException(status.getStatus(), sb.toString(), exception));
return Mono.error(new ElasticsearchStatusException(sb.toString(), status, exception));
}
return Mono.just(content);
@@ -1102,6 +1102,58 @@ public interface ReactiveElasticsearchClient {
*/
Mono<Void> refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest);
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param consumer never {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(Consumer)}
*/
@Deprecated
default Mono<Boolean> updateMapping(
Consumer<org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest> consumer) {
return putMapping(consumer);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(PutMappingRequest)}
*/
@Deprecated
default Mono<Boolean> updateMapping(
org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) {
return putMapping(putMappingRequest);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
*
* @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}.
* @param putMappingRequest must not be {@literal null}.
* @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index
* does not exist.
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html"> Indices
* Put Mapping API on elastic.co</a>
* @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)}
*/
@Deprecated
default Mono<Boolean> updateMapping(HttpHeaders headers,
org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) {
return putMapping(headers, putMappingRequest);
}
/**
* Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the
* {@literal indices} API.
@@ -15,11 +15,8 @@
*/
package org.springframework.data.elasticsearch.client.reactive;
import java.util.function.Function;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Utility class for common access to reactive Elasticsearch clients. {@link ReactiveRestClients} consolidates set up
@@ -64,21 +61,4 @@ public final class ReactiveRestClients {
return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator);
}
/**
* {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure
* the ReactiveElasticsearchClient with a {@link WebClient}
*
* @since 4.3
*/
public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback<WebClient> {
static WebClientConfigurationCallback from(Function<WebClient, WebClient> webClientCallback) {
Assert.notNull(webClientCallback, "webClientCallback must not be null");
// noinspection NullableProblems
return webClientCallback::apply;
}
}
}
@@ -20,15 +20,9 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.client.analytics.InferencePipelineAggregationBuilder;
import org.elasticsearch.client.analytics.ParsedInference;
import org.elasticsearch.client.analytics.ParsedStringStats;
import org.elasticsearch.client.analytics.ParsedTopMetrics;
import org.elasticsearch.client.analytics.StringStatsAggregationBuilder;
import org.elasticsearch.client.analytics.TopMetricsAggregationBuilder;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ParseField;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.adjacency.AdjacencyMatrixAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.adjacency.ParsedAdjacencyMatrix;
@@ -50,8 +44,6 @@ import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregati
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedAutoDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedVariableWidthHistogram;
import org.elasticsearch.search.aggregations.bucket.histogram.VariableWidthHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.missing.ParsedMissing;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
@@ -68,7 +60,12 @@ import org.elasticsearch.search.aggregations.bucket.range.ParsedRange;
import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler;
import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.*;
import org.elasticsearch.search.aggregations.pipeline.*;
import org.elasticsearch.search.suggest.Suggest;
@@ -84,7 +81,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsea
* <p>
* Original implementation source {@link org.elasticsearch.client.RestHighLevelClient#getDefaultNamedXContents()} by
* {@literal Elasticsearch} (<a href="https://www.elastic.co">https://www.elastic.co</a>) licensed under the Apache
* License, Version 2.0. The latest version used from Elasticsearch is 7.10.2.
* License, Version 2.0.
* </p>
* Modified for usage with {@link ReactiveElasticsearchClient}.
* <p>
@@ -129,13 +126,9 @@ public class NamedXContents {
map.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c));
map.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c));
map.put(AutoDateHistogramAggregationBuilder.NAME, (p, c) -> ParsedAutoDateHistogram.fromXContent(p, (String) c));
map.put(VariableWidthHistogramAggregationBuilder.NAME,
(p, c) -> ParsedVariableWidthHistogram.fromXContent(p, (String) c));
map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
map.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c));
map.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c));
map.put(LongRareTerms.NAME, (p, c) -> ParsedLongRareTerms.fromXContent(p, (String) c));
map.put(StringRareTerms.NAME, (p, c) -> ParsedStringRareTerms.fromXContent(p, (String) c));
map.put(MissingAggregationBuilder.NAME, (p, c) -> ParsedMissing.fromXContent(p, (String) c));
map.put(NestedAggregationBuilder.NAME, (p, c) -> ParsedNested.fromXContent(p, (String) c));
map.put(ReverseNestedAggregationBuilder.NAME, (p, c) -> ParsedReverseNested.fromXContent(p, (String) c));
@@ -149,15 +142,10 @@ public class NamedXContents {
map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c));
map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c));
map.put(AdjacencyMatrixAggregationBuilder.NAME, (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c));
map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c));
map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c));
map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c));
map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c));
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
map.put(CompositeAggregationBuilder.NAME, (p, c) -> ParsedComposite.fromXContent(p, (String) c));
map.put(StringStatsAggregationBuilder.NAME, (p, c) -> ParsedStringStats.PARSER.parse(p, (String) c));
map.put(TopMetricsAggregationBuilder.NAME, (p, c) -> ParsedTopMetrics.PARSER.parse(p, (String) c));
map.put(InferencePipelineAggregationBuilder.NAME, (p, c) -> ParsedInference.fromXContent(p, (String) (c)));
List<NamedXContentRegistry.Entry> entries = map.entrySet().stream().map(
entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
@@ -78,8 +78,10 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.uid.Versions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
@@ -88,8 +90,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.rankeval.RankEvalRequest;
@@ -336,13 +336,8 @@ public class RequestConverters {
public static Request index(IndexRequest indexRequest) {
String method = Strings.hasLength(indexRequest.id()) ? HttpMethod.PUT.name() : HttpMethod.POST.name();
boolean isCreate = (indexRequest.opType() == DocWriteRequest.OpType.CREATE);
String endpoint;
if (indexRequest.opType() == DocWriteRequest.OpType.CREATE) {
endpoint = indexRequest.type().equals("_doc") ? endpoint(indexRequest.index(), "_create", indexRequest.id())
: endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(), "_create");
} else {
endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id());
}
String endpoint = endpoint(indexRequest.index(), indexRequest.type(), indexRequest.id(),
isCreate ? "_create" : null);
Request request = new Request(method, endpoint);
Params parameters = new Params(request);
@@ -367,9 +362,7 @@ public class RequestConverters {
}
public static Request update(UpdateRequest updateRequest) {
String endpoint = updateRequest.type().equals("_doc")
? endpoint(updateRequest.index(), "_update", updateRequest.id())
: endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
String endpoint = endpoint(updateRequest.index(), updateRequest.type(), updateRequest.id(), "_update");
Request request = new Request(HttpMethod.POST.name(), endpoint);
Params parameters = new Params(request);
@@ -507,11 +500,8 @@ public class RequestConverters {
}
public static Request explain(ExplainRequest explainRequest) {
String endpoint = explainRequest.type().equals("_doc")
? endpoint(explainRequest.index(), "_explain", explainRequest.id())
: endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain");
Request request = new Request(HttpMethod.GET.name(),
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), endpoint));
endpoint(explainRequest.index(), explainRequest.type(), explainRequest.id(), "_explain"));
Params params = new Params(request);
params.withStoredFields(explainRequest.storedFields());
@@ -851,7 +841,8 @@ public class RequestConverters {
RequestConverters.Params parameters = new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout());
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -862,7 +853,8 @@ public class RequestConverters {
new RequestConverters.Params(request) //
.withTimeout(putMappingRequest.timeout()) //
.withMasterTimeout(putMappingRequest.masterNodeTimeout());
.withMasterTimeout(putMappingRequest.masterNodeTimeout()) //
.withIncludeTypeName(false);
request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE));
return request;
}
@@ -888,6 +880,7 @@ public class RequestConverters {
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
@@ -900,6 +893,7 @@ public class RequestConverters {
parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout());
parameters.withIndicesOptions(getMappingsRequest.indicesOptions());
parameters.withLocal(getMappingsRequest.local());
parameters.withIncludeTypeName(false);
return request;
}
@@ -1006,6 +1000,7 @@ public class RequestConverters {
RequestConverters.Params parameters = new Params(request);
parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions());
parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults());
parameters.withIncludeTypeName(false);
return request;
}
@@ -1377,6 +1372,18 @@ public class RequestConverters {
}
return this;
}
/**
* sets the include_type_name parameter. Needed for Elasticsearch 7 to be used with the mapping types still
* available. Will be removed again when Elasticsearch drops the support for this parameter in Elasticsearch 8.
*
* @deprecated since 4.0
* @since 4.0
*/
@Deprecated
Params withIncludeTypeName(boolean includeTypeName) {
return putParam("include_type_name", String.valueOf(includeTypeName));
}
}
/**
@@ -1445,8 +1452,7 @@ public class RequestConverters {
// encode each part (e.g. index, type and id) separately before merging them into the path
// we prepend "/" to the path part to make this path absolute, otherwise there can be issues with
// paths that start with `-` or contain `:`
// the authority must be an empty string and not null, else paths that being with slashes could have them
URI uri = new URI((String) null, "", "/" + pathPart, (String) null, (String) null);
URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null);
// manually encode any slash that each part may contain
return uri.getRawPath().substring(1).replaceAll("/", "%2F");
} catch (URISyntaxException e) {
@@ -21,11 +21,13 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.search.Scroll;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Mutable state object holding scrollId to be used for scroll requests.
* Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)}
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
@@ -26,6 +26,7 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.RefreshPolicy;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
@@ -71,7 +72,6 @@ public class ElasticsearchConfigurationSupport {
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
mappingContext.setWriteTypeHints(writeTypeHints());
return mappingContext;
}
@@ -171,17 +171,4 @@ public class ElasticsearchConfigurationSupport {
protected FieldNamingStrategy fieldNamingStrategy() {
return PropertyNameFieldNamingStrategy.INSTANCE;
}
/**
* Flag specifiying if type hints (_class fields) should be written in the index. It is strongly advised to keep the
* default value of {@literal true}. If you need to write to an existing index that does not have a mapping defined
* for these fields and that has a strict mapping set, then it might be necessary to disable type hints. But notice
* that in this case reading polymorphic types may fail.
*
* @return flag if type hints should be written
* @since 4.3
*/
protected boolean writeTypeHints() {
return true;
}
}
@@ -35,6 +35,7 @@ public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
registerBeanDefinitionParser("repositories", parser);
registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser());
registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser());
registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser());
}
@@ -41,22 +41,32 @@ public @interface EnableElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
* @deprecated since 4.1
*/
@Deprecated
String dateTimeProviderRef() default "";
}
@@ -41,22 +41,32 @@ public @interface EnableReactiveElasticsearchAuditing {
/**
* Configures the {@link AuditorAware} bean to be used to lookup the current principal.
*
* @return
*/
String auditorAwareRef() default "";
/**
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
*
* @return
*/
boolean setDates() default true;
/**
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
*
* @return
*/
boolean modifyOnCreate() default true;
/**
* Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be
* used for setting creation and modification dates.
*
* @return
* @deprecated since 4.1
*/
@Deprecated
String dateTimeProviderRef() default "";
}
@@ -0,0 +1,57 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.config;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.elasticsearch.client.NodeClientFactoryBean;
import org.w3c.dom.Element;
/**
* NodeClientBeanDefinitionParser
*
* @author Rizwan Idrees
* @author Mohsin Husen
*/
public class NodeClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(NodeClientFactoryBean.class);
setLocalSettings(element, builder);
return getSourcedBeanDefinition(builder, element, parserContext);
}
private void setLocalSettings(Element element, BeanDefinitionBuilder builder) {
builder.addPropertyValue("local", Boolean.valueOf(element.getAttribute("local")));
builder.addPropertyValue("clusterName", element.getAttribute("cluster-name"));
builder.addPropertyValue("enableHttp", Boolean.valueOf(element.getAttribute("http-enabled")));
builder.addPropertyValue("pathData", element.getAttribute("path-data"));
builder.addPropertyValue("pathHome", element.getAttribute("path-home"));
builder.addPropertyValue("pathConfiguration", element.getAttribute("path-configuration"));
}
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}
@@ -29,7 +29,7 @@ import org.springframework.data.mapping.context.PersistentEntities;
* @author Peter-Josef Meisch
* @since 4.1
*/
public class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
private final MappingElasticsearchConverter converter;
@@ -17,10 +17,14 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.util.StringUtils.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
@@ -32,6 +36,7 @@ import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -42,7 +47,9 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.0
*/
abstract class AbstractIndexTemplate implements IndexOperations {
abstract class AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDefaultIndexOperations.class);
protected final ElasticsearchConverter elasticsearchConverter;
protected final RequestFactory requestFactory;
@@ -50,7 +57,7 @@ abstract class AbstractIndexTemplate implements IndexOperations {
@Nullable protected final Class<?> boundClass;
@Nullable private final IndexCoordinates boundIndex;
public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
Assert.notNull(boundClass, "boundClass may not be null");
@@ -60,7 +67,7 @@ abstract class AbstractIndexTemplate implements IndexOperations {
this.boundIndex = null;
}
public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) {
Assert.notNull(boundIndex, "boundIndex may not be null");
@@ -169,6 +176,31 @@ abstract class AbstractIndexTemplate implements IndexOperations {
protected abstract void doRefresh(IndexCoordinates indexCoordinates);
@Override
@Deprecated
public boolean addAlias(AliasQuery query) {
return doAddAlias(query, getIndexCoordinates());
}
@Deprecated
protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index);
@Override
public List<AliasMetadata> queryForAlias() {
return doQueryForAlias(getIndexCoordinates());
}
protected abstract List<AliasMetadata> doQueryForAlias(IndexCoordinates index);
@Override
@Deprecated
public boolean removeAlias(AliasQuery query) {
return doRemoveAlias(query, getIndexCoordinates());
}
@Deprecated
protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index);
@Override
public Map<String, Set<AliasData>> getAliases(String... aliasNames) {
@@ -202,7 +234,6 @@ abstract class AbstractIndexTemplate implements IndexOperations {
// load mapping specified in Mapping annotation if present
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
String mappingPath = mappingAnnotation.mappingPath();
@@ -212,6 +243,8 @@ abstract class AbstractIndexTemplate implements IndexOperations {
if (hasText(mappings)) {
return Document.parse(mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}
@@ -1,225 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.Version;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.util.Assert;
/**
* This class contains methods that are common the implementations derived from {@link AbstractElasticsearchTemplate}
* using either the {@link org.elasticsearch.client.transport.TransportClient} or the
* {@link org.elasticsearch.client.RestHighLevelClient} and that use Elasticsearch specific libraries.
* <p>
* <strong>Note:</strong> Although this class is public, it is not considered to be part of the official Spring Data
* Elasticsearch API and so might change at any time.
*
* @author Peter-Josef Meisch
*/
public abstract class AbstractElasticsearchRestTransportTemplate extends AbstractElasticsearchTemplate {
// region DocumentOperations
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
failedDocuments);
}
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
DocWriteResponse response = bulkItemResponse.getResponse();
if (response != null) {
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
response.getVersion());
} else {
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
}
}).collect(Collectors.toList());
}
// endregion
// region SearchOperations
protected <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index) {
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(
new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(),
clazz, index);
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(
callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), getEntityCreator(documentCallback))));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz)));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
IndexCoordinates index = getIndexCoordinatesFor(entityClass);
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
IndexCoordinates index) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
ReadDocumentCallback<?> documentCallback = new ReadDocumentCallback<>(elasticsearchConverter, entityClass, index);
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
// endregion
// region helper
@Override
public Query matchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
}
@Override
public Query idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {})))
.build();
}
@Override
protected String getVendor() {
return "Elasticsearch";
}
@Override
protected String getRuntimeLibraryVersion() {
return Version.CURRENT.toString();
}
@Override
@Deprecated
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
protected <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
}
// endregion
}
@@ -15,17 +15,32 @@
*/
package org.springframework.data.elasticsearch.core;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.elasticsearch.BulkFailureException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
@@ -42,6 +57,7 @@ import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
@@ -57,28 +73,20 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* This class contains methods that are common to different implementations of the {@link ElasticsearchOperations}
* interface that use different clients, like TransportClient, RestHighLevelClient and the next Java client from
* Elasticsearch or some future implementation that might use an Opensearch client. This class must not contain imports
* or use classes that are specific to one of these implementations.
* <p>
* <strong>Note:</strong> Although this class is public, it is not considered to be part of the official Spring Data
* Elasticsearch API and so might change at any time.
* AbstractElasticsearchTemplate
*
* @author Sascha Woo
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
* @author Subhobrata Dey
* @author Steven Pearce
* @author Anton Naydenov
*/
public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
@Nullable protected ElasticsearchConverter elasticsearchConverter;
@Nullable protected RequestFactory requestFactory;
@Nullable protected EntityOperations entityOperations;
@Nullable protected EntityCallbacks entityCallbacks;
@Nullable protected RefreshPolicy refreshPolicy;
@Nullable private EntityOperations entityOperations;
@Nullable private EntityCallbacks entityCallbacks;
@Nullable private RefreshPolicy refreshPolicy;
@Nullable protected RoutingResolver routingResolver;
// region Initialization
@@ -93,10 +101,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext);
requestFactory = new RequestFactory(elasticsearchConverter);
// initialize the VersionInfo class in the initialization phase
// noinspection ResultOfMethodCallIgnored
VersionInfo.versionProperties();
VersionInfo.logVersions(getClusterVersion());
}
/**
@@ -111,7 +116,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
copy.setRoutingResolver(routingResolver);
copy.setRefreshPolicy(refreshPolicy);
return copy;
}
@@ -162,16 +166,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
/**
* logs the versions of the different Elasticsearch components.
*
* @since 4.3
*/
public void logVersions() {
VersionInfo.logVersions(getVendor(), getRuntimeLibraryVersion(), getClusterVersion());
}
// endregion
// region DocumentOperations
@@ -194,7 +188,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
doIndex(query, index);
T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index);
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
return entityAfterAfterSave;
}
@@ -221,18 +215,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
List<IndexQuery> indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery)
.collect(Collectors.toList());
if (indexQueries.isEmpty()) {
return Collections.emptyList();
if (!indexQueries.isEmpty()) {
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
entities.forEach(entity -> updateIndexedObject(entity, iterator.next()));
}
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
// noinspection unchecked
return indexQueries.stream() //
.map(IndexQuery::getObject) //
.map(entity -> (T) updateIndexedObject(entity, iterator.next())) //
.collect(Collectors.toList()); //
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
}
@Override
@@ -357,6 +346,40 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index);
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
if (refreshPolicy == null) {
return request;
}
return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param requestBuilder must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequestBuilder<R>> R prepareWriteRequestBuilder(R requestBuilder) {
if (refreshPolicy == null) {
return requestBuilder;
}
return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
// endregion
// region SearchOperations
@@ -373,8 +396,8 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Override
public <T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index) {
Duration scrollTime = query.getScrollTime() != null ? query.getScrollTime() : Duration.ofMinutes(1);
long scrollTimeInMillis = scrollTime.toMillis();
long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis();
// noinspection ConstantConditions
int maxCount = query.isLimiting() ? query.getMaxResults() : 0;
@@ -395,16 +418,96 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
return doSearch(query, clazz, index);
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index);
}
protected abstract <T> SearchHits<T> doSearch(MoreLikeThisQuery query, Class<T> clazz, IndexCoordinates index);
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz) {
return multiSearch(queries, clazz, getIndexCoordinatesFor(clazz));
}
@Override
public <T> List<SearchHits<T>> multiSearch(List<? extends Query> queries, Class<T> clazz, IndexCoordinates index) {
MultiSearchRequest request = new MultiSearchRequest();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, clazz, index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse())));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
Class<?> clazz = it.next();
request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz)));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
getIndexCoordinatesFor(entityClass));
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public List<SearchHits<?>> multiSearch(List<? extends Query> queries, List<Class<?>> classes,
IndexCoordinates index) {
Assert.notNull(queries, "queries must not be null");
Assert.notNull(classes, "classes must not be null");
Assert.notNull(index, "index must not be null");
Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size");
MultiSearchRequest request = new MultiSearchRequest();
Iterator<Class<?>> it = classes.iterator();
for (Query query : queries) {
request.add(requestFactory.searchRequest(query, it.next(), index));
}
MultiSearchResponse.Item[] items = getMultiSearchResult(request);
List<SearchHits<?>> res = new ArrayList<>(queries.size());
int c = 0;
Iterator<Class<?>> it1 = classes.iterator();
for (Query query : queries) {
Class entityClass = it1.next();
SearchDocumentResponseCallback<SearchHits<?>> callback = new ReadSearchDocumentResponseCallback<>(entityClass,
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response)));
}
return res;
}
@Override
public <T> SearchHits<T> search(Query query, Class<T> clazz) {
return search(query, clazz, getIndexCoordinatesFor(clazz));
@@ -434,6 +537,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
*/
abstract protected void searchScrollClear(List<String> scrollIds);
abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request);
@Override
public SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz) {
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
// endregion
// region Helper methods
@@ -470,7 +580,38 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
}
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
/**
* @param bulkResponse
* @return the list of the item id's
*/
protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkResponse bulkResponse) {
if (bulkResponse.hasFailures()) {
Map<String, String> failedDocuments = new HashMap<>();
for (BulkItemResponse item : bulkResponse.getItems()) {
if (item.isFailed())
failedDocuments.put(item.getId(), item.getFailureMessage());
}
throw new BulkFailureException(
"Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages ["
+ failedDocuments + ']',
failedDocuments);
}
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
DocWriteResponse response = bulkItemResponse.getResponse();
if (response != null) {
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
response.getVersion());
} else {
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
}
}).collect(Collectors.toList());
}
protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
@@ -480,30 +621,22 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
}
return entity;
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
@@ -588,18 +721,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
@Nullable
abstract protected String getClusterVersion();
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
abstract protected String getVendor();
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
abstract protected String getRuntimeLibraryVersion();
// endregion
// region Entity callbacks
@@ -686,16 +807,13 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void updateIndexedObjectsWithQueries(List<?> queries,
List<IndexedObjectInformation> indexedObjectInformations) {
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i)));
updateIndexedObject(queryObject, indexedObjectInformations.get(i));
}
}
}
@@ -730,10 +848,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, document, index);
}
}
@@ -755,7 +869,6 @@ 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());
@@ -776,7 +889,6 @@ 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());
@@ -1,41 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
/**
* Class corresponding to the Elasticsearch class, but in the org.springframework.data.elasticsearch package
*
* @author Peter-Josef Meisch
*/
public class ActiveShardCount {
private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2;
private static final int ALL_ACTIVE_SHARDS = -1;
public static final ActiveShardCount DEFAULT = new ActiveShardCount(ACTIVE_SHARD_COUNT_DEFAULT);
public static final ActiveShardCount ALL = new ActiveShardCount(ALL_ACTIVE_SHARDS);
public static final ActiveShardCount NONE = new ActiveShardCount(0);
public static final ActiveShardCount ONE = new ActiveShardCount(1);
private final int value;
public ActiveShardCount(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
/**
* Aggregation container used in the Spring Data Elasticsearch API for a single aggregation. The concrete
* implementations must be provided by the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregation class from the used client implementation.
* @since 4.3
*/
public interface AggregationContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregation();
}
@@ -1,31 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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;
/**
* Aggregations container used in the Spring Data Elasticsearch API. The concrete implementations must be provided by
* the code handling the direct communication with Elasticsearch.
*
* @author Peter-Josef Meisch
* @param <T> the aggregations class from the used client implementation.
* @since 4.3
*/
public interface AggregationsContainer<T> {
/**
* @return the concrete aggregations implementation
*/
T aggregations();
}
@@ -165,35 +165,20 @@ class CriteriaQueryProcessor {
@Nullable
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
QueryBuilder query = null;
String fieldName = field.getName();
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
OperationKey key = entry.getKey();
// operations without a value
switch (key) {
case EXISTS:
query = existsQuery(fieldName);
break;
case EMPTY:
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
break;
case NOT_EMPTY:
query = wildcardQuery(fieldName, "*");
break;
default:
break;
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
}
if (query != null) {
return query;
}
// now operation keys with a value
Object value = entry.getValue();
String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null;
switch (key) {
case EQUALS:
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -27,6 +28,7 @@ import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.client.GetAliasesResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
@@ -37,6 +39,7 @@ import org.elasticsearch.client.indices.GetMappingsRequest;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,6 +53,7 @@ import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -61,18 +65,18 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.0
*/
class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations {
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(RestIndexTemplate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class);
private final ElasticsearchRestTemplate restTemplate;
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
super(restTemplate.getElasticsearchConverter(), boundClass);
this.restTemplate = restTemplate;
}
public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) {
super(restTemplate.getElasticsearchConverter(), boundIndex);
this.restTemplate = restTemplate;
}
@@ -137,6 +141,40 @@ class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations
});
}
@Override
@Deprecated
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index);
return restTemplate
.execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
@Deprecated
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequest indicesAliasesRequest = requestFactory.indicesRemoveAliasesRequest(query, index);
return restTemplate.execute(
client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged());
}
@Override
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
return restTemplate.execute(client -> {
GetAliasesResponse alias = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT);
// we only return data for the first index name that was requested (always have done so)
String index1 = getAliasesRequest.indices()[0];
return new ArrayList<>(alias.getAliases().get(index1));
});
}
@Override
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
@@ -49,8 +49,8 @@ import org.springframework.data.elasticsearch.core.index.AliasData;
import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest;
import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest;
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.ReactiveMappingBuilder;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
@@ -63,9 +63,9 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.1
*/
class ReactiveIndexTemplate implements ReactiveIndexOperations {
class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveIndexTemplate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class);
@Nullable private final Class<?> boundClass;
private final IndexCoordinates boundIndex;
@@ -73,7 +73,7 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
private final ReactiveElasticsearchOperations operations;
private final ElasticsearchConverter converter;
public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(index, "index must not be null");
@@ -85,7 +85,7 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
this.boundIndex = index;
}
public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, Class<?> clazz) {
public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class<?> clazz) {
Assert.notNull(operations, "operations must not be null");
Assert.notNull(clazz, "clazz must not be null");
@@ -183,14 +183,11 @@ class ReactiveIndexTemplate implements ReactiveIndexOperations {
Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class);
if (mappingAnnotation != null) {
String mappingPath = mappingAnnotation.mappingPath();
if (hasText(mappingPath)) {
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
}
return loadDocument(mappingAnnotation.mappingPath(), "@Mapping");
}
return new ReactiveMappingBuilder(converter).buildReactivePropertyMapping(clazz).map(Document::parse);
String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz);
return Mono.just(Document.parse(mapping));
}
@Override
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
@@ -58,6 +59,7 @@ import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -69,18 +71,19 @@ import org.springframework.util.Assert;
* @author George Popides
* @since 4.0
*/
class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOperations {
class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
private static final Logger LOGGER = LoggerFactory.getLogger(TransportIndexTemplate.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTransportIndexOperations.class);
private final Client client;
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, Class<?> boundClass) {
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
Class<?> boundClass) {
super(elasticsearchConverter, boundClass);
this.client = client;
}
public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter,
IndexCoordinates boundIndex) {
super(elasticsearchConverter, boundIndex);
this.client = client;
@@ -143,6 +146,31 @@ class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOpera
return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap();
}
@Override
protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = requestFactory.aliasAction(query, index);
return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged();
}
@Override
@Deprecated
protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory
.indicesRemoveAliasesRequestBuilder(client, query, index);
return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged();
}
@Override
protected List<AliasMetadata> doQueryForAlias(IndexCoordinates index) {
GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index);
return client.admin().indices().getAliases(getAliasesRequest).actionGet().getAliases().get(index.getIndexName());
}
@Override
protected Map<String, Set<AliasData>> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) {
@@ -15,7 +15,6 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.Collection;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -122,8 +121,6 @@ public interface DocumentOperations {
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned
* @return list of {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.1
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz);
@@ -135,8 +132,6 @@ public interface DocumentOperations {
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return list of {@link MultiGetItem}s
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
*/
<T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
@@ -164,7 +159,6 @@ public interface DocumentOperations {
* @param queries the queries to execute in bulk
* @param clazz the entity class
* @return the information about the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, Class<?> clazz) {
@@ -176,7 +170,6 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
return bulkIndex(queries, BulkOptions.defaultOptions(), index);
@@ -189,7 +182,6 @@ public interface DocumentOperations {
* @param bulkOptions options to be added to the bulk request
* @param clazz the entity class
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, Class<?> clazz);
@@ -200,7 +192,6 @@ public interface DocumentOperations {
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @return the information about of the indexed objects
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -208,7 +199,6 @@ public interface DocumentOperations {
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
default void bulkUpdate(List<UpdateQuery> queries, IndexCoordinates index) {
bulkUpdate(queries, BulkOptions.defaultOptions(), index);
@@ -219,7 +209,6 @@ public interface DocumentOperations {
*
* @param clazz the entity class
* @param queries the queries to execute in bulk
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.1
*/
void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz);
@@ -229,7 +218,6 @@ public interface DocumentOperations {
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
*/
void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -295,7 +283,7 @@ public interface DocumentOperations {
/**
* 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}
@@ -22,13 +22,13 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.rest.RestStatus;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.RestStatusException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -38,7 +38,7 @@ import org.springframework.util.StringUtils;
* Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an
* appropriate exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is
* appropriate: any other exception may have resulted from user code, and should not be translated.
*
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Roman Puchkovskiy
@@ -63,28 +63,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
ex);
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
}
if (ex instanceof RestStatusException) {
RestStatusException restStatusException = (RestStatusException) ex;
Throwable cause = restStatusException.getCause();
if (cause instanceof ElasticsearchException) {
ElasticsearchException elasticsearchException = (ElasticsearchException) cause;
if (!indexAvailable(elasticsearchException)) {
return new NoSuchIndexException(ObjectUtils.nullSafeToString(elasticsearchException.getMetadata("es.index")),
ex);
}
}
}
if (ex instanceof ValidationException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
@@ -99,26 +80,13 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
private boolean isSeqNoConflict(Exception exception) {
Integer status = null;
String message = null;
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
status = statusException.status().getStatus();
message = statusException.getMessage();
}
if (exception instanceof RestStatusException) {
RestStatusException statusException = (RestStatusException) exception;
status = statusException.getStatus();
message = statusException.getMessage();
}
if (status != null && message != null) {
return status == 409 && message.contains("type=version_conflict_engine_exception")
&& message.contains("version conflict, required seqNo");
return statusException.status() == RestStatus.CONFLICT && statusException.getMessage() != null
&& statusException.getMessage().contains("type=version_conflict_engine_exception")
&& statusException.getMessage().contains("version conflict, required seqNo");
}
if (exception instanceof VersionConflictEngineException) {
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,11 +33,10 @@ import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
@@ -93,7 +92,7 @@ import org.springframework.util.Assert;
* @author Massimiliano Poggi
* @author Farid Faoudi
*/
public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTransportTemplate {
public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class);
@@ -132,7 +131,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.notNull(clazz, "clazz must not be null");
return new RestIndexTemplate(this, clazz);
return new DefaultIndexOperations(this, clazz);
}
@Override
@@ -140,7 +139,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.notNull(index, "index must not be null");
return new RestIndexTemplate(this, index);
return new DefaultIndexOperations(this, index);
}
// endregion
@@ -158,10 +157,9 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT));
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(),
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())));
updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(),
indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
}
return indexResponse.getId();
@@ -170,7 +168,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
@Override
@Nullable
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index);
GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT));
@@ -182,6 +179,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Id defined for Query");
MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index);
MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT));
@@ -223,8 +221,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(query, clazz, index);
return ResponseConverter
.byQueryResponseOf(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
return ByQueryResponse.of(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT)));
}
@Override
@@ -262,7 +259,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
final BulkByScrollResponse bulkByScrollResponse = execute(
client -> client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT));
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
return ByQueryResponse.of(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
@@ -273,24 +270,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
return indexedObjectInformationList;
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequest<R>> R prepareWriteRequest(R request) {
if (refreshPolicy == null) {
return request;
}
return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
// endregion
// region SearchOperations
@@ -316,10 +295,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
SearchRequest searchRequest = requestFactory.searchRequest(query, clazz, index);
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -333,10 +310,9 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
SearchResponse response = execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -348,10 +324,9 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
SearchResponse response = execute(client -> client.scroll(request, RequestOptions.DEFAULT));
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = //
new ReadSearchScrollDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -378,8 +353,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
return items;
}
// endregion
// region ClientCallback
/**
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
@@ -15,10 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.elasticsearch.action.ActionFuture;
@@ -36,11 +33,9 @@ import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequestBuilder;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilder;
@@ -92,7 +87,7 @@ import org.springframework.util.Assert;
* @deprecated as of 4.0
*/
@Deprecated
public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTemplate {
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
private static final Logger QUERY_LOGGER = LoggerFactory
.getLogger("org.springframework.data.elasticsearch.core.QUERY");
private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class);
@@ -136,7 +131,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
Assert.notNull(clazz, "clazz must not be null");
return new TransportIndexTemplate(client, elasticsearchConverter, clazz);
return new DefaultTransportIndexOperations(client, elasticsearchConverter, clazz);
}
@Override
@@ -144,7 +139,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
Assert.notNull(index, "index must not be null");
return new TransportIndexTemplate(client, elasticsearchConverter, index);
return new DefaultTransportIndexOperations(client, elasticsearchConverter, index);
}
// endregion
@@ -182,8 +177,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion())));
updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion()));
}
return documentId;
@@ -205,6 +200,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
public <T> List<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index) {
Assert.notNull(index, "index must not be null");
Assert.notEmpty(query.getIds(), "No Ids defined for Query");
MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, clazz, index);
@@ -247,8 +243,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
@Override
public ByQueryResponse delete(Query query, Class<?> clazz, IndexCoordinates index) {
return ResponseConverter
.byQueryResponseOf(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get());
return ByQueryResponse.of(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get());
}
@Override
@@ -290,45 +285,18 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
// UpdateByQueryRequestBuilder has not parameters to set a routing value
final BulkByScrollResponse bulkByScrollResponse = updateByQueryRequestBuilder.execute().actionGet();
return ResponseConverter.byQueryResponseOf(bulkByScrollResponse);
return ByQueryResponse.of(bulkByScrollResponse);
}
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
IndexCoordinates index) {
// do it in batches; test code on some machines kills the transport node when the size gets too much
Collection<? extends List<?>> queryLists = partitionBasedOnSize(queries, 2500);
List<IndexedObjectInformation> allIndexedObjectInformations = new ArrayList<>(queries.size());
queryLists.forEach(queryList -> {
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queryList, bulkOptions, index);
bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder);
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
bulkRequestBuilder.execute().actionGet());
updateIndexedObjectsWithQueries(queryList, indexedObjectInformations);
allIndexedObjectInformations.addAll(indexedObjectInformations);
});
return allIndexedObjectInformations;
BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);
bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder);
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
bulkRequestBuilder.execute().actionGet());
updateIndexedObjectsWithQueries(queries, indexedObjectInformations);
return indexedObjectInformations;
}
/**
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param requestBuilder must not be {@literal null}.
* @param <R>
* @return the processed {@link WriteRequest}.
*/
protected <R extends WriteRequestBuilder<R>> R prepareWriteRequestBuilder(R requestBuilder) {
if (refreshPolicy == null) {
return requestBuilder;
}
return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy));
}
// endregion
// region SearchOperations
@@ -352,9 +320,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, clazz, index);
SearchResponse response = getSearchResponse(searchRequestBuilder);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -369,10 +336,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
SearchResponse response = getSearchResponseWithTimeout(action);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -386,10 +352,9 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
SearchResponse response = getSearchResponseWithTimeout(action);
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
return callback.doWith(SearchDocumentResponse.from(response));
}
@Override
@@ -446,11 +411,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
public Client getClient() {
return client;
}
<T> Collection<List<T>> partitionBasedOnSize(List<T> inputList, int size) {
final AtomicInteger counter = new AtomicInteger(0);
return inputList.stream().collect(Collectors.groupingBy(s -> counter.getAndIncrement() / size)).values();
}
// endregion
/**
@@ -93,6 +93,22 @@ class EntityOperations {
return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver);
}
/**
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
*
* @param entity the entity to determine the index name. Can be {@literal null} if {@code index} and {@literal type}
* are provided.
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(Entity, String)}
*/
@Deprecated
IndexCoordinates determineIndex(Entity<?> entity, @Nullable String index, @Nullable String type) {
return determineIndex(entity.getPersistentEntity(), index, type);
}
/**
* Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using
* preferred values for index and type if provided, otherwise fall back to index and type defined on entity level.
@@ -106,6 +122,24 @@ class EntityOperations {
return determineIndex(entity.getPersistentEntity(), index);
}
/**
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
* defined on entity level.
*
* @param persistentEntity the entity to determine the index name. Can be {@literal null} if {@code index} and
* {@literal type} are provided.
* @param index index name override can be {@literal null}.
* @param type index type override can be {@literal null}.
* @return the {@link IndexCoordinates} containing index name and index type.
* @deprecated since 4.1, use {@link EntityOperations#determineIndex(ElasticsearchPersistentEntity, String)}
*/
@Deprecated
IndexCoordinates determineIndex(ElasticsearchPersistentEntity<?> persistentEntity, @Nullable String index,
@Nullable String type) {
return determineIndex(persistentEntity, index);
}
/**
* Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type}
* overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type
@@ -200,6 +234,25 @@ class EntityOperations {
*/
interface AdaptibleEntity<T> extends Entity<T> {
/**
* Returns whether the entity has a parent.
*
* @return {@literal true} if the entity has a parent that has an {@literal id}.
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
@Deprecated
boolean hasParent();
/**
* Returns the parent Id. Can be {@literal null}.
*
* @return can be {@literal null}.
* @deprecated since 4.1, not supported anymore by Elasticsearch
*/
@Deprecated
@Nullable
Object getParentId();
/**
* Populates the identifier of the backing entity if it has an identifier property and there's no identifier
* currently present.
@@ -286,6 +339,24 @@ class EntityOperations {
return map.get(ID_FIELD);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#hasParent()
*/
@Override
public boolean hasParent() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getParentId()
*/
@Override
public Entity<?> getParentId() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object)
@@ -477,6 +548,7 @@ class EntityOperations {
*/
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
private final T bean;
private final ElasticsearchPersistentEntity<?> entity;
private final ConvertingPropertyAccessor<T> propertyAccessor;
private final IdentifierAccessor identifierAccessor;
@@ -489,6 +561,7 @@ class EntityOperations {
super(entity, identifierAccessor, propertyAccessor);
this.bean = bean;
this.entity = entity;
this.propertyAccessor = propertyAccessor;
this.identifierAccessor = identifierAccessor;
@@ -509,8 +582,16 @@ class EntityOperations {
}
@Override
public T getBean() {
return propertyAccessor.getBean();
public boolean hasParent() {
return getRequiredPersistentEntity().getParentIdProperty() != null;
}
@Deprecated
@Override
public Object getParentId() {
ElasticsearchPersistentProperty parentProperty = getRequiredPersistentEntity().getParentIdProperty();
return propertyAccessor.getProperty(parentProperty);
}
@Nullable
@@ -587,7 +668,7 @@ class EntityOperations {
@Override
public String getRouting() {
String routing = routingResolver.getRouting(propertyAccessor.getBean());
String routing = routingResolver.getRouting(bean);
if (routing != null) {
return routing;
@@ -19,6 +19,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.data.elasticsearch.core.index.AliasData;
@@ -29,6 +30,7 @@ import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.index.TemplateData;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.lang.Nullable;
/**
@@ -188,6 +190,35 @@ public interface IndexOperations {
// endregion
// region aliases
/**
* Add an alias.
*
* @param query query defining the alias
* @return true if the alias was created
* @deprecated since 4.1 use {@link #alias(AliasActions)}
*/
@Deprecated
boolean addAlias(AliasQuery query);
/**
* Get the alias information for a specified index.
*
* @return alias information
* @deprecated since 4.1, use {@link #getAliases(String...)} or {@link #getAliasesForIndex(String...)}.
*/
@Deprecated
List<AliasMetadata> queryForAlias();
/**
* Remove an alias.
*
* @param query query defining the alias
* @return true if the alias was removed
* @deprecated since 4.1 use {@link #alias(AliasActions)}
*/
@Deprecated
boolean removeAlias(AliasQuery query);
/**
* Executes the given {@link AliasActions}.
*
@@ -25,12 +25,12 @@ import org.springframework.lang.Nullable;
* @since 4.1
*/
public class IndexedObjectInformation {
@Nullable private final String id;
private final String id;
@Nullable private final Long seqNo;
@Nullable private final Long primaryTerm;
@Nullable private final Long version;
private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
this.id = id;
this.seqNo = seqNo;
@@ -38,12 +38,11 @@ public class IndexedObjectInformation {
this.version = version;
}
public static IndexedObjectInformation of(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
return new IndexedObjectInformation(id, seqNo, primaryTerm, version);
}
@Nullable
public String getId() {
return id;
}
@@ -1,442 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.Optional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Simple value object to work with ranges and boundaries.
*
* @author Sascha Woo
* @since 4.3
*/
public class Range<T> {
private final static Range<?> UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED);
/**
* The lower bound of the range.
*/
private final Bound<T> lowerBound;
/**
* The upper bound of the range.
*/
private final Bound<T> upperBound;
/**
* Creates a new {@link Range} with inclusive bounds for both values.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> closed(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.inclusive(to));
}
/**
* Creates a new Range with the given value as sole member.
*
* @param <T>
* @param value must not be {@literal null}.
* @return
* @see Range#closed(T, T)
*/
public static <T> Range<T> just(T value) {
return Range.closed(value, value);
}
/**
* Creates a new left-open {@link Range}, i.e. left exclusive, right inclusive.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> leftOpen(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.inclusive(to));
}
/**
* Creates a left-unbounded {@link Range} (the left bound set to {@link Bound#unbounded()}) with the given right
* bound.
*
* @param <T>
* @param to the right {@link Bound}, must not be {@literal null}.
* @return
*/
public static <T> Range<T> leftUnbounded(Bound<T> to) {
return new Range<>(Bound.unbounded(), to);
}
/**
* Creates a new {@link Range} with the given lower and upper bound.
*
* @param lowerBound must not be {@literal null}.
* @param upperBound must not be {@literal null}.
*/
public static <T> Range<T> of(Bound<T> lowerBound, Bound<T> upperBound) {
return new Range<>(lowerBound, upperBound);
}
/**
* Creates a new {@link Range} with exclusive bounds for both values.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> open(T from, T to) {
return new Range<>(Bound.exclusive(from), Bound.exclusive(to));
}
/**
* Creates a new right-open {@link Range}, i.e. left inclusive, right exclusive.
*
* @param <T>
* @param from must not be {@literal null}.
* @param to must not be {@literal null}.
* @return
*/
public static <T> Range<T> rightOpen(T from, T to) {
return new Range<>(Bound.inclusive(from), Bound.exclusive(to));
}
/**
* Creates a right-unbounded {@link Range} (the right bound set to {@link Bound#unbounded()}) with the given left
* bound.
*
* @param <T>
* @param from the left {@link Bound}, must not be {@literal null}.
* @return
*/
public static <T> Range<T> rightUnbounded(Bound<T> from) {
return new Range<>(from, Bound.unbounded());
}
/**
* Returns an unbounded {@link Range}.
*
* @return
*/
@SuppressWarnings("unchecked")
public static <T> Range<T> unbounded() {
return (Range<T>) UNBOUNDED;
}
private Range(Bound<T> lowerBound, Bound<T> upperBound) {
Assert.notNull(lowerBound, "Lower bound must not be null!");
Assert.notNull(upperBound, "Upper bound must not be null!");
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
/**
* Returns whether the {@link Range} contains the given value.
*
* @param value must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
public boolean contains(T value) {
Assert.notNull(value, "Reference value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
boolean greaterThanLowerBound = lowerBound.getValue() //
.map(it -> lowerBound.isInclusive() ? ((Comparable<? super T>) it).compareTo(value) <= 0
: ((Comparable<? super T>) it).compareTo(value) < 0) //
.orElse(true);
boolean lessThanUpperBound = upperBound.getValue() //
.map(it -> upperBound.isInclusive() ? ((Comparable<? super T>) it).compareTo(value) >= 0
: ((Comparable<? super T>) it).compareTo(value) > 0) //
.orElse(true);
return greaterThanLowerBound && lessThanUpperBound;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Range)) {
return false;
}
Range<?> range = (Range<?>) o;
if (!ObjectUtils.nullSafeEquals(lowerBound, range.lowerBound)) {
return false;
}
return ObjectUtils.nullSafeEquals(upperBound, range.upperBound);
}
public Range.Bound<T> getLowerBound() {
return this.lowerBound;
}
public Range.Bound<T> getUpperBound() {
return this.upperBound;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(lowerBound);
result = 31 * result + ObjectUtils.nullSafeHashCode(upperBound);
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s-%s", lowerBound.toPrefixString(), upperBound.toSuffixString());
}
/**
* Value object representing a boundary. A boundary can either be {@link #unbounded() unbounded}, {@link #inclusive(T)
* including its value} or {@link #exclusive(T) its value}.
*/
public static final class Bound<T> {
@SuppressWarnings({ "rawtypes", "unchecked" }) //
private static final Bound<?> UNBOUNDED = new Bound(Optional.empty(), true);
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<T> value;
private final boolean inclusive;
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Double> exclusive(double value) {
return exclusive((Double) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Float> exclusive(float value) {
return exclusive((Float) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Integer> exclusive(int value) {
return exclusive((Integer) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Long> exclusive(long value) {
return exclusive((Long) value);
}
/**
* Creates a boundary excluding {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static <T> Bound<T> exclusive(T value) {
Assert.notNull(value, "Value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
return new Bound<>(Optional.of(value), false);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Double> inclusive(double value) {
return inclusive((Double) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Float> inclusive(float value) {
return inclusive((Float) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Integer> inclusive(int value) {
return inclusive((Integer) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static Bound<Long> inclusive(long value) {
return inclusive((Long) value);
}
/**
* Creates a boundary including {@code value}.
*
* @param value must not be {@literal null}.
* @return
*/
public static <T> Bound<T> inclusive(T value) {
Assert.notNull(value, "Value must not be null!");
Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!");
return new Bound<>(Optional.of(value), true);
}
/**
* Creates an unbounded {@link Bound}.
*/
@SuppressWarnings("unchecked")
public static <T> Bound<T> unbounded() {
return (Bound<T>) UNBOUNDED;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Bound(Optional<T> value, boolean inclusive) {
this.value = value;
this.inclusive = inclusive;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Bound)) {
return false;
}
Bound<?> bound = (Bound<?>) o;
if (inclusive != bound.inclusive)
return false;
return ObjectUtils.nullSafeEquals(value, bound.value);
}
public Optional<T> getValue() {
return this.value;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(value);
result = 31 * result + (inclusive ? 1 : 0);
return result;
}
/**
* Returns whether this boundary is bounded.
*
* @return
*/
public boolean isBounded() {
return value.isPresent();
}
public boolean isInclusive() {
return this.inclusive;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return value.map(Object::toString).orElse("unbounded");
}
String toPrefixString() {
return getValue() //
.map(Object::toString) //
.map(it -> isInclusive() ? "[".concat(it) : "(".concat(it)) //
.orElse("unbounded");
}
String toSuffixString() {
return getValue() //
.map(Object::toString) //
.map(it -> isInclusive() ? it.concat("]") : it.concat(")")) //
.orElse("unbounded");
}
}
}
@@ -106,8 +106,7 @@ public interface ReactiveDocumentOperations {
/**
* Index entities in the given {@literal index}. If the {@literal index} is {@literal null} or empty the index name
* provided via entity metadata is used. On errors returns with
* {@link org.springframework.data.elasticsearch.BulkFailureException} with information about the failed operation
* provided via entity metadata is used.
*
* @param entities must not be {@literal null}.
* @param index the target index, must not be {@literal null}
@@ -149,8 +148,6 @@ public interface ReactiveDocumentOperations {
* @param query the query defining the ids of the objects to get
* @param clazz the type of the object to be returned, used to determine the index
* @return flux with list of {@link MultiGetItem}s that contain the entities
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.1
*/
<T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz);
@@ -162,15 +159,12 @@ public interface ReactiveDocumentOperations {
* @param clazz the type of the object to be returned
* @param index the index(es) from which the objects are read.
* @return flux with list of {@link MultiGetItem}s that contain the entities
* @see Query#multiGetQuery(Collection)
* @see Query#multiGetQueryWithRouting(List)
* @since 4.0
*/
<T> Flux<MultiGetItem<T>> multiGet(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Bulk update all objects. Will do update. On errors returns with
* {@link org.springframework.data.elasticsearch.BulkFailureException} with information about the failed operation
* Bulk update all objects. Will do update.
*
* @param queries the queries to execute in bulk
* @since 4.0
@@ -184,7 +178,6 @@ public interface ReactiveDocumentOperations {
*
* @param queries the queries to execute in bulk
* @param bulkOptions options to be added to the bulk request
* @throws org.springframework.data.elasticsearch.BulkFailureException with information about the failed operation
* @since 4.0
*/
Mono<Void> bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index);
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2022 the original author or authors.
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.Version;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
@@ -40,10 +39,11 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
@@ -57,7 +57,6 @@ import org.springframework.data.elasticsearch.NoSuchIndexException;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregation;
import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
@@ -76,14 +75,12 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMa
import org.springframework.data.elasticsearch.core.query.BulkOptions;
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.core.query.UpdateResponse;
import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver;
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.support.VersionInfo;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
@@ -145,9 +142,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
this.operations = new EntityOperations(this.mappingContext);
this.requestFactory = new RequestFactory(converter);
// initialize the VersionInfo class in the initialization phase
// noinspection ResultOfMethodCallIgnored
VersionInfo.versionProperties();
logVersions();
}
private ReactiveElasticsearchTemplate copy() {
@@ -160,18 +155,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return copy;
}
/**
* logs the versions of the different Elasticsearch components.
*
* @return a Mono signalling finished execution
* @since 4.3
*/
public Mono<Void> logVersions() {
return getVendor() //
.doOnNext(vendor -> getRuntimeLibraryVersion() //
.doOnNext(runtimeLibraryVersion -> getClusterVersion() //
.doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) //
.then(); //
private void logVersions() {
getClusterVersion() //
.doOnSuccess(VersionInfo::logVersions) //
.doOnError(e -> VersionInfo.logVersions(null)) //
.subscribe();
}
@Override
@@ -287,42 +275,27 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(entity.getClass());
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntityFor(entity.getClass());
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
} else {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
}
return entity;
}
@@ -337,6 +310,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
Assert.notNull(index, "Index must not be null");
Assert.notNull(clazz, "Class must not be null");
Assert.notNull(query, "Query must not be null");
Assert.notEmpty(query.getIds(), "No Id define for Query");
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
@@ -374,7 +348,8 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
protected Flux<BulkItemResponse> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
BulkRequest bulkRequest = prepareWriteRequest(requestFactory.bulkRequest(queries, bulkOptions, index));
return client.bulk(bulkRequest) //
.onErrorMap(e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest, e)) //
.onErrorMap(
e -> new UncategorizedElasticsearchException("Error while bulk for request: " + bulkRequest.toString(), e)) //
.flatMap(this::checkForBulkOperationFailure) //
.flatMapMany(response -> Flux.fromArray(response.getItems()));
}
@@ -482,7 +457,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it)));
}
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
@@ -562,7 +537,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
Assert.notNull(query, "Query must not be null!");
return doDeleteBy(query, entityType, index).map(ResponseConverter::byQueryResponseOf);
return doDeleteBy(query, entityType, index).map(ByQueryResponse::of);
}
@Override
@@ -656,7 +631,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. E.g. by setting the
* Customization hook to modify a generated {@link DeleteRequest} prior to its execution. Eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request the generated {@link DeleteRequest}.
@@ -667,7 +642,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link DeleteByQueryRequest} prior to its execution. E.g. by setting the
* Customization hook to modify a generated {@link DeleteByQueryRequest} prior to its execution. Eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request the generated {@link DeleteByQueryRequest}.
@@ -692,7 +667,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link IndexRequest} prior to its execution. E.g. by setting the
* Customization hook to modify a generated {@link IndexRequest} prior to its execution. Eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param source the source object the {@link IndexRequest} was derived from.
@@ -704,7 +679,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Preprocess the write request before it is sent to the server, e.g. by setting the
* Pre process the write request before it is sent to the server, eg. by setting the
* {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable.
*
* @param request must not be {@literal null}.
@@ -757,108 +732,61 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
private Flux<SearchDocument> doFind(Query query, Class<?> clazz, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
boolean useScroll = !(query.getPageable().isPaged() || query.isLimiting());
request = prepareSearchRequest(request, useScroll);
request = prepareSearchRequest(request);
if (useScroll) {
return doScroll(request);
} else {
if (query.getPageable().isPaged() || query.isLimiting()) {
return doFind(request);
} else {
return doScroll(request);
}
});
}
private <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request, false);
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
// noinspection unchecked
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> ((Mono<T>) documentCallback
.toEntity(searchDocument)).toFuture();
return doFindForResponse(request, entityCreator);
request = prepareSearchRequest(request);
return doFindForResponse(request);
});
}
@Override
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType) {
public Flux<Aggregation> aggregate(Query query, Class<?> entityType) {
return aggregate(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(entityType, "entityType must not be null");
Assert.notNull(index, "index must not be null");
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request, false);
return doAggregate(request);
});
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @return a {@link Flux} emitting the result of the operation.
*/
protected Flux<AggregationContainer<?>> doAggregate(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doCount: {}", request);
}
return Flux.from(execute(client -> client.aggregate(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty()).map(ElasticsearchAggregation::new);
public Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index) {
return doAggregate(query, entityType, index);
}
@Override
public Mono<Suggest> suggest(Query query, Class<?> entityType) {
return suggest(query, entityType, getIndexCoordinatesFor(entityType));
}
@Override
public Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index) {
Assert.notNull(query, "query must not be null");
Assert.notNull(entityType, "entityType must not be null");
Assert.notNull(index, "index must not be null");
return doFindForResponse(query, entityType, index).mapNotNull(searchDocumentResponse -> {
Suggest suggest = searchDocumentResponse.getSuggest();
SearchHitMapping.mappingFor(entityType, converter).mapHitsInCompletionSuggestion(suggest);
return suggest;
});
}
@Override
@Deprecated
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
public Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType) {
return doSuggest(suggestion, getIndexCoordinatesFor(entityType));
}
@Override
@Deprecated
public Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
public Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index) {
return doSuggest(suggestion, index);
}
@Deprecated
private Flux<org.elasticsearch.search.suggest.Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
private Flux<Suggest> doSuggest(SuggestBuilder suggestion, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(suggestion, index);
return Flux.from(execute(client -> client.suggest(request)));
});
}
private Flux<Aggregation> doAggregate(Query query, Class<?> entityType, IndexCoordinates index) {
return Flux.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request);
return doAggregate(request);
});
}
@Override
public Mono<Long> count(Query query, Class<?> entityType) {
return count(query, entityType, getIndexCoordinatesFor(entityType));
@@ -873,7 +801,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, entityType, index);
request = prepareSearchRequest(request, false);
request = prepareSearchRequest(request);
return doCount(request);
});
}
@@ -898,18 +826,31 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* Customization hook on the actual execution result {@link Mono}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @param entityCreator
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
*/
protected <T> Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
SearchDocumentResponse.EntityCreator<T> entityCreator) {
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
}
return Mono.from(execute(client -> client.searchForResponse(request)))
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(SearchDocumentResponse::from);
}
/**
* Customization hook on the actual execution result {@link Publisher}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @return a {@link Flux} emitting the result of the operation.
*/
protected Flux<Aggregation> doAggregate(SearchRequest request) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doCount: {}", request);
}
return Flux.from(execute(client -> client.aggregate(request))) //
.onErrorResume(NoSuchIndexException.class, it -> Flux.empty());
}
/**
@@ -945,25 +886,19 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
/**
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. E.g. by setting the
* Customization hook to modify a generated {@link SearchRequest} prior to its execution. Eg. by setting the
* {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable.
*
* @param request the generated {@link SearchRequest}.
* @param useScroll
* @return never {@literal null}.
*/
protected SearchRequest prepareSearchRequest(SearchRequest request, boolean useScroll) {
protected SearchRequest prepareSearchRequest(SearchRequest request) {
if (indicesOptions != null) {
request = request.indicesOptions(indicesOptions);
if (indicesOptions == null) {
return request;
}
// request_cache is not allowed on scroll requests.
if (useScroll) {
request = request.requestCache(null);
}
return request;
return request.indicesOptions(indicesOptions);
}
// endregion
@@ -971,41 +906,11 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
// region Helper methods
protected Mono<String> getClusterVersion() {
try {
return Mono.from(execute(ReactiveElasticsearchClient::info))
.map(mainResponse -> mainResponse.getVersion().toString());
return Mono.from(execute(client -> client.info())).map(mainResponse -> mainResponse.getVersion().toString());
} catch (Exception ignored) {}
return Mono.empty();
}
/**
* @return the vendor name of the used cluster and client library
* @since 4.3
*/
protected Mono<String> getVendor() {
return Mono.just("Elasticsearch");
}
/**
* @return the version of the used client runtime library.
* @since 4.3
*/
protected Mono<String> getRuntimeLibraryVersion() {
return Mono.just(Version.CURRENT.toString());
}
@Override
public Query matchAllQuery() {
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build();
}
@Override
public Query idsQuery(List<String> ids) {
Assert.notNull(ids, "ids must not be null");
return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {})))
.build();
}
// endregion
@Override
@@ -1030,12 +935,12 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
@Override
public ReactiveIndexOperations indexOps(IndexCoordinates index) {
return new ReactiveIndexTemplate(this, index);
return new DefaultReactiveIndexOperations(this, index);
}
@Override
public ReactiveIndexOperations indexOps(Class<?> clazz) {
return new ReactiveIndexTemplate(this, clazz);
return new DefaultReactiveIndexOperations(this, clazz);
}
@Override
@@ -1184,19 +1089,17 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallAfterConvert(entity, document, index);
}
}
protected interface SearchDocumentCallback<T> {
Mono<T> toEntity(SearchDocument response);
@NonNull
Mono<T> toEntity(@NonNull SearchDocument response);
Mono<SearchHit<T>> toSearchHit(SearchDocument response);
@NonNull
Mono<SearchHit<T>> toSearchHit(@NonNull SearchDocument response);
}
protected class ReadSearchDocumentCallback<T> implements SearchDocumentCallback<T> {
@@ -1239,7 +1142,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
private T entityAt(long index) {
// it's safe to cast to int because the original indexed collection was fitting in memory
// it's safe to cast to int because the original indexed colleciton was fitting in memory
int intIndex = (int) index;
return entities.get(intIndex);
}
@@ -63,7 +63,7 @@ public abstract class ReactiveResourceUtil {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append('\n');
sb.append(line);
}
sink.next(sb.toString());
@@ -18,13 +18,14 @@ package org.springframework.data.elasticsearch.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.core.query.StringQuery;
/**
* The reactive operations for the
@@ -44,7 +45,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Mono} emitting the nr of matching documents.
*/
default Mono<Long> count(Class<?> entityType) {
return count(matchAllQuery(), entityType);
return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType);
}
/**
@@ -123,6 +124,7 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
@@ -136,6 +138,7 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
@@ -148,6 +151,7 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
@@ -162,6 +166,7 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
@@ -180,7 +185,7 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType);
Flux<Aggregation> aggregate(Query query, Class<?> entityType);
/**
* Perform an aggregation specified by the given {@link Query query}. <br />
@@ -191,73 +196,23 @@ public interface ReactiveSearchOperations {
* @return a {@link Flux} emitting matching aggregations one by one.
* @since 4.0
*/
Flux<AggregationContainer<?>> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
Flux<Aggregation> aggregate(Query query, Class<?> entityType, IndexCoordinates index);
/**
* Does a suggest query
*
* @param suggestion the query
* @param entityType must not be {@literal null}.
* @return the suggest response (Elasticsearch library classes)
* @deprecated since 4.3, use {@link #suggest(Query, Class)}
* @return the suggest response
*/
@Deprecated
Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
Flux<Suggest> suggest(SuggestBuilder suggestion, Class<?> entityType);
/**
* Does a suggest query
*
* @param suggestion the query
* @param index the index to run the query against
* @return the suggest response (Elasticsearch library classes)
* @deprecated since 4.3, use {@link #suggest(Query, Class, IndexCoordinates)}
* @return the suggest response
*/
@Deprecated
Flux<org.elasticsearch.search.suggest.Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
/**
* Does a suggest query.
*
* @param query the Query containing the suggest definition. Must be currently a
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQuery}, must not be {@literal null}.
* @param entityType the type of the entities that might be returned for a completion suggestion, must not be
* {@literal null}.
* @return suggest data
* @since 4.3
*/
Mono<Suggest> suggest(Query query, Class<?> entityType);
/**
* Does a suggest query.
*
* @param query the Query containing the suggest definition. Must be currently a
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQuery}, must not be {@literal null}.
* @param entityType the type of the entities that might be returned for a completion suggestion, must not be
* {@literal null}.
* @param index the index to run the query against, must not be {@literal null}.
* @return suggest data
* @since 4.3
*/
Mono<Suggest> suggest(Query query, Class<?> entityType, IndexCoordinates index);
// region helper
/**
* Creates a {@link Query} to find all documents. Must be implemented by the concrete implementations to provide an
* appropriate query using the respective client.
*
* @return a query to find all documents
* @since 4.3
*/
Query matchAllQuery();
/**
* Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete
* implementations to provide an appropriate query using the respective client.
*
* @param ids the list of ids must not be {@literal null}
* @return query returning the documents with the given ids
* @since 4.3
*/
Query idsQuery(List<String> ids);
// endregion
Flux<Suggest> suggest(SuggestBuilder suggestion, IndexCoordinates index);
}
@@ -18,17 +18,14 @@ package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.util.CollectionUtils.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.admin.indices.alias.Alias;
@@ -54,7 +51,6 @@ import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
@@ -70,7 +66,7 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
@@ -82,6 +78,7 @@ import org.elasticsearch.index.reindex.UpdateByQueryAction;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
@@ -139,6 +136,37 @@ class RequestFactory {
}
// region alias
@Deprecated
public IndicesAliasesRequest.AliasActions aliasAction(AliasQuery query, IndexCoordinates index) {
Assert.notNull(index, "No index defined for Alias");
Assert.notNull(query.getAliasName(), "No alias defined");
String[] indexNames = index.getIndexNames();
IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.add()
.alias(query.getAliasName()).indices(indexNames);
if (query.getFilterBuilder() != null) {
aliasAction.filter(query.getFilterBuilder());
} else if (query.getFilter() != null) {
aliasAction.filter(query.getFilter());
}
if (!StringUtils.isEmpty(query.getRouting())) {
aliasAction.routing(query.getRouting());
}
if (!StringUtils.isEmpty(query.getSearchRouting())) {
aliasAction.searchRouting(query.getSearchRouting());
}
if (!StringUtils.isEmpty(query.getIndexRouting())) {
aliasAction.indexRouting(query.getIndexRouting());
}
return aliasAction;
}
public GetAliasesRequest getAliasesRequest(IndexCoordinates index) {
String[] indexNames = index.getIndexNames();
@@ -154,6 +182,14 @@ class RequestFactory {
return getAliasesRequest;
}
@Deprecated
public IndicesAliasesRequest indicesAddAliasesRequest(AliasQuery query, IndexCoordinates index) {
IndicesAliasesRequest.AliasActions aliasAction = aliasAction(query, index);
IndicesAliasesRequest request = new IndicesAliasesRequest();
request.addAliasAction(aliasAction);
return request;
}
public IndicesAliasesRequest indicesAliasesRequest(AliasActions aliasActions) {
IndicesAliasesRequest request = new IndicesAliasesRequest();
@@ -221,6 +257,27 @@ class RequestFactory {
indicesAliasesRequest(aliasActions).getAliasActions().forEach(requestBuilder::addAliasAction);
return requestBuilder;
}
@Deprecated
public IndicesAliasesRequest indicesRemoveAliasesRequest(AliasQuery query, IndexCoordinates index) {
String[] indexNames = index.getIndexNames();
IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.remove() //
.indices(indexNames) //
.alias(query.getAliasName());
return Requests.indexAliasesRequest() //
.addAliasAction(aliasAction);
}
@Deprecated
IndicesAliasesRequestBuilder indicesRemoveAliasesRequestBuilder(Client client, AliasQuery query,
IndexCoordinates index) {
String indexName = index.getIndexName();
return client.admin().indices().prepareAliases().removeAlias(indexName, query.getAliasName());
}
// endregion
// region bulk
@@ -228,15 +285,15 @@ class RequestFactory {
BulkRequest bulkRequest = new BulkRequest();
if (bulkOptions.getTimeout() != null) {
bulkRequest.timeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis()));
bulkRequest.timeout(bulkOptions.getTimeout());
}
if (bulkOptions.getRefreshPolicy() != null) {
bulkRequest.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy()));
bulkRequest.setRefreshPolicy(bulkOptions.getRefreshPolicy());
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequest.waitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue()));
bulkRequest.waitForActiveShards(bulkOptions.getWaitForActiveShards());
}
if (bulkOptions.getPipeline() != null) {
@@ -263,15 +320,15 @@ class RequestFactory {
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
if (bulkOptions.getTimeout() != null) {
bulkRequestBuilder.setTimeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis()));
bulkRequestBuilder.setTimeout(bulkOptions.getTimeout());
}
if (bulkOptions.getRefreshPolicy() != null) {
bulkRequestBuilder.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy()));
bulkRequestBuilder.setRefreshPolicy(bulkOptions.getRefreshPolicy());
}
if (bulkOptions.getWaitForActiveShards() != null) {
bulkRequestBuilder.setWaitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue()));
bulkRequestBuilder.setWaitForActiveShards(bulkOptions.getWaitForActiveShards());
}
if (bulkOptions.getPipeline() != null) {
@@ -298,8 +355,7 @@ class RequestFactory {
// region index management
public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map<String, Object> settings,
@Nullable Document mapping) {
public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map<String, Object> settings, @Nullable Document mapping) {
Assert.notNull(index, "index must not be null");
Assert.notNull(settings, "settings must not be null");
@@ -677,16 +733,15 @@ class RequestFactory {
FetchSourceContext fetchSourceContext = getFetchSourceContext(searchQuery);
if (!isEmpty(searchQuery.getIdsWithRouting())) {
if (!isEmpty(searchQuery.getIds())) {
String indexName = index.getIndexName();
for (String id : searchQuery.getIds()) {
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, id);
for (Query.IdWithRouting idWithRouting : searchQuery.getIdsWithRouting()) {
MultiGetRequest.Item item = new MultiGetRequest.Item(indexName, idWithRouting.getId());
if (idWithRouting.getRouting() != null) {
item = item.routing(idWithRouting.getRouting());
if (searchQuery.getRoute() != null) {
item = item.routing(searchQuery.getRoute());
}
// note: multiGet does not have fields, need to set sourceContext to filter
if (fetchSourceContext != null) {
item.fetchSourceContext(fetchSourceContext);
}
@@ -705,17 +760,15 @@ class RequestFactory {
String indexName = index.getIndexName();
IndexRequest indexRequest;
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
if (query.getObject() != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
// If we have a query id and a document id, do not ask ES to generate one.
if (id != null) {
indexRequest = new IndexRequest(indexName).id(id);
} else {
indexRequest = new IndexRequest(indexName);
}
indexRequest.source(elasticsearchConverter.mapObject(queryObject).toJson(), Requests.INDEX_CONTENT_TYPE);
indexRequest.source(elasticsearchConverter.mapObject(query.getObject()).toJson(), Requests.INDEX_CONTENT_TYPE);
} else if (query.getSource() != null) {
indexRequest = new IndexRequest(indexName).id(query.getId()).source(query.getSource(),
Requests.INDEX_CONTENT_TYPE);
@@ -726,8 +779,7 @@ class RequestFactory {
if (query.getVersion() != null) {
indexRequest.version(query.getVersion());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
queryObject != null ? queryObject.getClass() : null);
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
indexRequest.versionType(versionType);
}
@@ -762,16 +814,15 @@ class RequestFactory {
IndexRequestBuilder indexRequestBuilder;
Object queryObject = query.getObject();
if (queryObject != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId();
if (query.getObject() != null) {
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
// If we have a query id and a document id, do not ask ES to generate one.
if (id != null) {
indexRequestBuilder = client.prepareIndex(indexName, type, id);
} else {
indexRequestBuilder = client.prepareIndex(indexName, type);
}
indexRequestBuilder.setSource(elasticsearchConverter.mapObject(queryObject).toJson(),
indexRequestBuilder.setSource(elasticsearchConverter.mapObject(query.getObject()).toJson(),
Requests.INDEX_CONTENT_TYPE);
} else if (query.getSource() != null) {
indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(),
@@ -783,8 +834,7 @@ class RequestFactory {
if (query.getVersion() != null) {
indexRequestBuilder.setVersion(query.getVersion());
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
queryObject != null ? queryObject.getClass() : null);
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
indexRequestBuilder.setVersionType(versionType);
}
@@ -807,18 +857,14 @@ class RequestFactory {
// region search
@Nullable
public HighlightBuilder highlightBuilder(Query query) {
HighlightBuilder highlightBuilder = query.getHighlightQuery()
.map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext())
.getHighlightBuilder(highlightQuery.getHighlight(), highlightQuery.getType()))
.orElse(null);
HighlightBuilder highlightBuilder = query.getHighlightQuery().map(HighlightQuery::getHighlightBuilder).orElse(null);
if (highlightBuilder == null) {
if (query instanceof NativeSearchQuery) {
NativeSearchQuery searchQuery = (NativeSearchQuery) query;
if ((searchQuery.getHighlightFields() != null && searchQuery.getHighlightFields().length > 0)
|| searchQuery.getHighlightBuilder() != null) {
if (searchQuery.getHighlightFields() != null || searchQuery.getHighlightBuilder() != null) {
highlightBuilder = searchQuery.getHighlightBuilder();
if (highlightBuilder == null) {
@@ -944,6 +990,11 @@ class RequestFactory {
sourceBuilder.seqNoAndPrimaryTerm(true);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (query.getPageable().isPaged()) {
sourceBuilder.from((int) query.getPageable().getOffset());
sourceBuilder.size(query.getPageable().getPageSize());
@@ -952,18 +1003,12 @@ class RequestFactory {
sourceBuilder.size(INDEX_MAX_RESULT_WINDOW);
}
if (query.getSourceFilter() != null) {
sourceBuilder.fetchSource(getFetchSourceContext(query));
SourceFilter sourceFilter = query.getSourceFilter();
sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (!query.getFields().isEmpty()) {
query.getFields().forEach(sourceBuilder::fetchField);
sourceBuilder.fetchSource(query.getFields().toArray(new String[0]), null);
}
if (query.getIndicesOptions() != null) {
request.indicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
request.indicesOptions(query.getIndicesOptions());
}
if (query.isLimiting()) {
@@ -979,7 +1024,7 @@ class RequestFactory {
request.preference(query.getPreference());
}
request.searchType(SearchType.fromString(query.getSearchType().name().toLowerCase()));
request.searchType(query.getSearchType());
prepareSort(query, sourceBuilder, getPersistentEntity(clazz));
@@ -1003,9 +1048,9 @@ class RequestFactory {
request.routing(query.getRoute());
}
Duration timeout = query.getTimeout();
TimeValue timeout = query.getTimeout();
if (timeout != null) {
sourceBuilder.timeout(new TimeValue(timeout.toMillis()));
sourceBuilder.timeout(timeout);
}
sourceBuilder.explain(query.getExplain());
@@ -1016,25 +1061,7 @@ class RequestFactory {
query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer(getQueryRescorerBuilder(rescorer)));
if (query.getRequestCache() != null) {
request.requestCache(query.getRequestCache());
}
if (!query.getRuntimeFields().isEmpty()) {
Map<String, Object> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping());
});
sourceBuilder.runtimeMappings(runtimeMappings);
}
if (query.getScrollTime() != null) {
request.scroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
}
request.source(sourceBuilder);
return request;
}
@@ -1046,13 +1073,18 @@ class RequestFactory {
Assert.notEmpty(indexNames, "No index defined for Query");
SearchRequestBuilder searchRequestBuilder = client.prepareSearch(indexNames) //
.setSearchType(SearchType.fromString(query.getSearchType().name().toLowerCase())) //
.setSearchType(query.getSearchType()) //
.setVersion(true) //
.setTrackScores(query.getTrackScores());
if (hasSeqNoPrimaryTermProperty(clazz)) {
searchRequestBuilder.seqNoAndPrimaryTerm(true);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (query.getPageable().isPaged()) {
searchRequestBuilder.setFrom((int) query.getPageable().getOffset());
searchRequestBuilder.setSize(query.getPageable().getPageSize());
@@ -1061,17 +1093,12 @@ class RequestFactory {
searchRequestBuilder.setSize(INDEX_MAX_RESULT_WINDOW);
}
if (query.getSourceFilter() != null) {
SourceFilter sourceFilter = query.getSourceFilter();
searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
if (!query.getFields().isEmpty()) {
query.getFields().forEach(searchRequestBuilder::addFetchField);
searchRequestBuilder.setFetchSource(query.getFields().toArray(new String[0]), null);
}
if (query.getIndicesOptions() != null) {
searchRequestBuilder.setIndicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions()));
searchRequestBuilder.setIndicesOptions(query.getIndicesOptions());
}
if (query.isLimiting()) {
@@ -1109,9 +1136,9 @@ class RequestFactory {
searchRequestBuilder.setRouting(query.getRoute());
}
Duration timeout = query.getTimeout();
TimeValue timeout = query.getTimeout();
if (timeout != null) {
searchRequestBuilder.setTimeout(new TimeValue(timeout.toMillis()));
searchRequestBuilder.setTimeout(timeout);
}
searchRequestBuilder.setExplain(query.getExplain());
@@ -1122,23 +1149,6 @@ class RequestFactory {
query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer(getQueryRescorerBuilder(rescorer)));
if (query.getRequestCache() != null) {
searchRequestBuilder.setRequestCache(query.getRequestCache());
}
if (!query.getRuntimeFields().isEmpty()) {
Map<String, Object> runtimeMappings = new HashMap<>();
query.getRuntimeFields().forEach(runtimeField -> {
runtimeMappings.put(runtimeField.getName(), runtimeField.getMapping());
});
searchRequestBuilder.setRuntimeMappings(runtimeMappings);
}
if (query.getScrollTime() != null) {
searchRequestBuilder.setScroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
}
return searchRequestBuilder;
}
@@ -1161,16 +1171,11 @@ class RequestFactory {
}
if (!isEmpty(query.getAggregations())) {
query.getAggregations().forEach(sourceBuilder::aggregation);
for (AbstractAggregationBuilder<?> aggregationBuilder : query.getAggregations()) {
sourceBuilder.aggregation(aggregationBuilder);
}
}
if (!isEmpty(query.getPipelineAggregations())) {
query.getPipelineAggregations().forEach(sourceBuilder::aggregation);
}
if (query.getSuggestBuilder() != null) {
sourceBuilder.suggest(query.getSuggestBuilder());
}
}
private void prepareNativeSearch(SearchRequestBuilder searchRequestBuilder, NativeSearchQuery nativeSearchQuery) {
@@ -1191,15 +1196,9 @@ class RequestFactory {
}
if (!isEmpty(nativeSearchQuery.getAggregations())) {
nativeSearchQuery.getAggregations().forEach(searchRequestBuilder::addAggregation);
}
if (!isEmpty(nativeSearchQuery.getPipelineAggregations())) {
nativeSearchQuery.getPipelineAggregations().forEach(searchRequestBuilder::addAggregation);
}
if (nativeSearchQuery.getSuggestBuilder() != null) {
searchRequestBuilder.suggest(nativeSearchQuery.getSuggestBuilder());
for (AbstractAggregationBuilder<?> aggregationBuilder : nativeSearchQuery.getAggregations()) {
searchRequestBuilder.addAggregation(aggregationBuilder);
}
}
}
@@ -1239,15 +1238,6 @@ class RequestFactory {
private SortBuilder<?> getSortBuilder(Sort.Order order, @Nullable ElasticsearchPersistentEntity<?> entity) {
SortOrder sortOrder = order.getDirection().isDescending() ? SortOrder.DESC : SortOrder.ASC;
Order.Mode mode = Order.DEFAULT_MODE;
String unmappedType = null;
if (order instanceof Order) {
Order o = (Order) order;
mode = o.getMode();
unmappedType = o.getUnmappedType();
}
if (ScoreSortBuilder.NAME.equals(order.getProperty())) {
return SortBuilders //
.scoreSort() //
@@ -1265,23 +1255,14 @@ class RequestFactory {
geoDistanceOrder.getGeoPoint().getLon());
sort.geoDistance(GeoDistance.fromString(geoDistanceOrder.getDistanceType().name()));
sort.sortMode(SortMode.fromString(mode.name()));
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
sort.sortMode(SortMode.fromString(geoDistanceOrder.getMode().name()));
sort.unit(DistanceUnit.fromString(geoDistanceOrder.getUnit()));
if (geoDistanceOrder.getIgnoreUnmapped() != GeoDistanceOrder.DEFAULT_IGNORE_UNMAPPED) {
sort.ignoreUnmapped(geoDistanceOrder.getIgnoreUnmapped());
}
return sort;
} else {
FieldSortBuilder sort = SortBuilders //
.fieldSort(fieldName) //
.order(sortOrder) //
.sortMode(SortMode.fromString(mode.name()));
if (unmappedType != null) {
sort.unmappedType(unmappedType);
}
.order(sortOrder);
if (order.getNullHandling() == Sort.NullHandling.NULLS_FIRST) {
sort.missing("_first");
@@ -1295,10 +1276,10 @@ class RequestFactory {
private QueryRescorerBuilder getQueryRescorerBuilder(RescorerQuery rescorerQuery) {
QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery());
Assert.notNull("queryBuilder", "Could not build query for rescorerQuery");
QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery());
Assert.notNull("queryBuilder", "Could not build query for rescorerQuery");
QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder);
QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder);
if (rescorerQuery.getScoreMode() != ScoreMode.Default) {
builder.setScoreMode(QueryRescoreMode.valueOf(rescorerQuery.getScoreMode().name()));
@@ -1472,7 +1453,8 @@ class RequestFactory {
public UpdateByQueryRequest updateByQueryRequest(UpdateQuery query, IndexCoordinates index) {
final UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(index.getIndexNames());
String indexName = index.getIndexName();
final UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexName);
updateByQueryRequest.setScript(getScript(query));
if (query.getAbortOnVersionConflict() != null) {
@@ -1489,7 +1471,7 @@ class RequestFactory {
updateByQueryRequest.setQuery(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequest.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions()));
updateByQueryRequest.setIndicesOptions(queryQuery.getIndicesOptions());
}
if (queryQuery.getScrollTime() != null) {
@@ -1564,8 +1546,7 @@ class RequestFactory {
updateByQueryRequestBuilder.filter(getQuery(queryQuery));
if (queryQuery.getIndicesOptions() != null) {
updateByQueryRequestBuilder.source()
.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions()));
updateByQueryRequestBuilder.source().setIndicesOptions(queryQuery.getIndicesOptions());
}
if (queryQuery.getScrollTime() != null) {
@@ -1667,33 +1648,26 @@ class RequestFactory {
}
}
@Nullable
private FetchSourceContext getFetchSourceContext(Query searchQuery) {
FetchSourceContext fetchSourceContext = null;
SourceFilter sourceFilter = searchQuery.getSourceFilter();
if (sourceFilter != null) {
return new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
if (!isEmpty(searchQuery.getFields())) {
if (sourceFilter == null) {
sourceFilter = new FetchSourceFilter(toArray(searchQuery.getFields()), null);
} else {
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, sourceFilter.getIncludes());
sourceFilter = new FetchSourceFilter(toArray(arrayList), null);
}
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
} else if (sourceFilter != null) {
fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes());
}
return null;
return fetchSourceContext;
}
public org.elasticsearch.action.support.IndicesOptions toElasticsearchIndicesOptions(IndicesOptions indicesOptions) {
Assert.notNull(indicesOptions, "indicesOptions must not be null");
Set<org.elasticsearch.action.support.IndicesOptions.Option> options = indicesOptions.getOptions().stream()
.map(it -> org.elasticsearch.action.support.IndicesOptions.Option.valueOf(it.name().toUpperCase()))
.collect(Collectors.toSet());
Set<org.elasticsearch.action.support.IndicesOptions.WildcardStates> wildcardStates = indicesOptions
.getExpandWildcards().stream()
.map(it -> org.elasticsearch.action.support.IndicesOptions.WildcardStates.valueOf(it.name().toUpperCase()))
.collect(Collectors.toSet());
return new org.elasticsearch.action.support.IndicesOptions(EnumSet.copyOf(options), EnumSet.copyOf(wildcardStates));
}
// endregion
@Nullable
@@ -1721,23 +1695,17 @@ class RequestFactory {
return null;
}
private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class<?> clazz) {
private VersionType retrieveVersionTypeFromPersistentEntity(Class<?> clazz) {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = elasticsearchConverter
.getMappingContext();
ElasticsearchPersistentEntity<?> persistentEntity = clazz != null ? mappingContext.getPersistentEntity(clazz)
: null;
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(clazz);
VersionType versionType = null;
if (persistentEntity != null) {
org.springframework.data.elasticsearch.annotations.Document.VersionType entityVersionType = persistentEntity
.getVersionType();
if (entityVersionType != null) {
versionType = VersionType.fromString(entityVersionType.name().toLowerCase());
}
versionType = persistentEntity.getVersionType();
}
return versionType != null ? versionType : VersionType.EXTERNAL;
@@ -28,7 +28,6 @@ import java.util.stream.Collectors;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.client.indices.GetIndexResponse;
@@ -38,14 +37,11 @@ import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.ScrollableHitSource;
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.query.ByQueryResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -317,71 +313,4 @@ public class ResponseConverter {
}
// endregion
// region byQueryResponse
public static ByQueryResponse byQueryResponseOf(BulkByScrollResponse bulkByScrollResponse) {
final List<ByQueryResponse.Failure> failures = bulkByScrollResponse.getBulkFailures() //
.stream() //
.map(ResponseConverter::byQueryResponseFailureOf) //
.collect(Collectors.toList()); //
final List<ByQueryResponse.SearchFailure> searchFailures = bulkByScrollResponse.getSearchFailures() //
.stream() //
.map(ResponseConverter::byQueryResponseSearchFailureOf) //
.collect(Collectors.toList());//
return ByQueryResponse.builder() //
.withTook(bulkByScrollResponse.getTook().getMillis()) //
.withTimedOut(bulkByScrollResponse.isTimedOut()) //
.withTotal(bulkByScrollResponse.getTotal()) //
.withUpdated(bulkByScrollResponse.getUpdated()) //
.withDeleted(bulkByScrollResponse.getDeleted()) //
.withBatches(bulkByScrollResponse.getBatches()) //
.withVersionConflicts(bulkByScrollResponse.getVersionConflicts()) //
.withNoops(bulkByScrollResponse.getNoops()) //
.withBulkRetries(bulkByScrollResponse.getBulkRetries()) //
.withSearchRetries(bulkByScrollResponse.getSearchRetries()) //
.withReasonCancelled(bulkByScrollResponse.getReasonCancelled()) //
.withFailures(failures) //
.withSearchFailure(searchFailures) //
.build(); //
}
/**
* Create a new {@link ByQueryResponse.Failure} from {@link BulkItemResponse.Failure}
*
* @param failure {@link BulkItemResponse.Failure} to translate
* @return a new {@link ByQueryResponse.Failure}
*/
public static ByQueryResponse.Failure byQueryResponseFailureOf(BulkItemResponse.Failure failure) {
return ByQueryResponse.Failure.builder() //
.withIndex(failure.getIndex()) //
.withType(failure.getType()) //
.withId(failure.getId()) //
.withStatus(failure.getStatus().getStatus()) //
.withAborted(failure.isAborted()) //
.withCause(failure.getCause()) //
.withSeqNo(failure.getSeqNo()) //
.withTerm(failure.getTerm()) //
.build(); //
}
/**
* Create a new {@link ByQueryResponse.SearchFailure} from {@link ScrollableHitSource.SearchFailure}
*
* @param searchFailure {@link ScrollableHitSource.SearchFailure} to translate
* @return a new {@link ByQueryResponse.SearchFailure}
*/
public static ByQueryResponse.SearchFailure byQueryResponseSearchFailureOf(
ScrollableHitSource.SearchFailure searchFailure) {
return ByQueryResponse.SearchFailure.builder() //
.withReason(searchFailure.getReason()) //
.withIndex(searchFailure.getIndex()) //
.withNodeId(searchFailure.getNodeId()) //
.withShardId(searchFailure.getShardId()) //
.withStatus(searchFailure.getStatus().getStatus()) //
.build(); //
}
// endregion
}
@@ -1,60 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Defines a runtime field to be added to a Query
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class RuntimeField {
private final String name;
private final String type;
private final String script;
public RuntimeField(String name, String type, String script) {
Assert.notNull(name, "name must not be null");
Assert.notNull(type, "type must not be null");
Assert.notNull(script, "script must not be null");
this.name = name;
this.type = type;
this.script = script;
}
public String getName() {
return name;
}
/**
* @return the mapping as a Map like it is needed for the Elasticsearch client
*/
public Map<String, Object> getMapping() {
Map<String, Object> map = new HashMap<>();
map.put("type", type);
map.put("script", script);
return map;
}
}
@@ -22,9 +22,9 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.elasticsearch.search.aggregations.Aggregations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
@@ -32,8 +32,6 @@ import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -46,7 +44,6 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Roman Puchkovskiy
* @author Matt Gilene
* @author Sascha Woo
* @since 4.0
*/
class SearchHitMapping<T> {
@@ -98,30 +95,10 @@ class SearchHitMapping<T> {
SearchHit<T> hit = mapHit(document, content);
searchHits.add(hit);
}
AggregationsContainer<?> aggregations = searchDocumentResponse.getAggregations();
Aggregations aggregations = searchDocumentResponse.getAggregations();
TotalHitsRelation totalHitsRelation = TotalHitsRelation.valueOf(searchDocumentResponse.getTotalHitsRelation());
Suggest suggest = searchDocumentResponse.getSuggest();
mapHitsInCompletionSuggestion(suggest);
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations, suggest);
}
@SuppressWarnings("unchecked")
public void mapHitsInCompletionSuggestion(@Nullable Suggest suggest) {
if (suggest != null) {
for (Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion : suggest
.getSuggestions()) {
if (suggestion instanceof CompletionSuggestion) {
CompletionSuggestion<T> completionSuggestion = (CompletionSuggestion<T>) suggestion;
for (CompletionSuggestion.Entry<T> entry : completionSuggestion.getEntries()) {
for (CompletionSuggestion.Entry.Option<T> option : entry.getOptions()) {
option.updateSearchHit(this::mapHit);
}
}
}
}
}
return new SearchHitsImpl<>(totalHits, totalHitsRelation, maxScore, scrollId, searchHits, aggregations);
}
SearchHit<T> mapHit(SearchDocument searchDocument, T content) {
@@ -196,7 +173,7 @@ class SearchHitMapping<T> {
*/
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
if (searchHits.isEmpty()) {
if (searchHits.getTotalHits() == 0) {
return searchHits;
}
@@ -237,11 +214,10 @@ class SearchHitMapping<T> {
searchHits.getMaxScore(), //
scrollId, //
convertedSearchHits, //
searchHits.getAggregations(), //
searchHits.getSuggest());
searchHits.getAggregations());
}
} catch (Exception e) {
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
LOGGER.warn("Could not map inner_hits", e);
}
return searchHits;
@@ -249,7 +225,7 @@ class SearchHitMapping<T> {
/**
* find a {@link ElasticsearchPersistentEntity} following the property chain defined by the nested metadata
*
*
* @param persistentEntity base entity
* @param nestedMetaData nested metadata
* @return A {@link ElasticsearchPersistentEntityWithNestedMetaData} containing the found entity or null together with
@@ -18,13 +18,13 @@ package org.springframework.data.elasticsearch.core;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
/**
* Encapsulates a list of {@link SearchHit}s with additional information from the search.
*
*
* @param <T> the result data class.
* @author Sascha Woo
* @since 4.0
@@ -35,7 +35,7 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
* @return the aggregations.
*/
@Nullable
AggregationsContainer<?> getAggregations();
Aggregations getAggregations();
/**
* @return the maximum score
@@ -78,21 +78,6 @@ public interface SearchHits<T> extends Streamable<SearchHit<T>> {
return !getSearchHits().isEmpty();
}
/**
* @return the suggest response
* @since 4.3
*/
@Nullable
Suggest getSuggest();
/**
* @return wether the {@link SearchHits} has a suggest response.
* @since 4.3
*/
default boolean hasSuggest() {
return getSuggest() != null;
}
/**
* @return an iterator for {@link SearchHit}
*/
@@ -18,7 +18,7 @@ package org.springframework.data.elasticsearch.core;
import java.util.Collections;
import java.util.List;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -39,8 +39,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
@Nullable private final String scrollId;
private final List<? extends SearchHit<T>> searchHits;
private final Lazy<List<SearchHit<T>>> unmodifiableSearchHits;
@Nullable private final AggregationsContainer<?> aggregations;
@Nullable private final Suggest suggest;
@Nullable private final Aggregations aggregations;
/**
* @param totalHits the number of total hits for the search
@@ -51,8 +50,7 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
* @param aggregations the aggregations if available
*/
public SearchHitsImpl(long totalHits, TotalHitsRelation totalHitsRelation, float maxScore, @Nullable String scrollId,
List<? extends SearchHit<T>> searchHits, @Nullable AggregationsContainer<?> aggregations,
@Nullable Suggest suggest) {
List<? extends SearchHit<T>> searchHits, @Nullable Aggregations aggregations) {
Assert.notNull(searchHits, "searchHits must not be null");
@@ -62,7 +60,6 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
this.scrollId = scrollId;
this.searchHits = searchHits;
this.aggregations = aggregations;
this.suggest = suggest;
this.unmodifiableSearchHits = Lazy.of(() -> Collections.unmodifiableList(searchHits));
}
@@ -92,23 +89,14 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
public List<SearchHit<T>> getSearchHits() {
return unmodifiableSearchHits.get();
}
// endregion
// region SearchHit access
@Override
public SearchHit<T> getSearchHit(int index) {
return searchHits.get(index);
}
@Override
@Nullable
public AggregationsContainer<?> getAggregations() {
return aggregations;
}
@Override
@Nullable
public Suggest getSuggest() {
return suggest;
}
// endregion
@Override
public String toString() {
@@ -121,4 +109,12 @@ public class SearchHitsImpl<T> implements SearchScrollHits<T> {
", aggregations=" + aggregations + //
'}';
}
// region aggregations
@Override
@Nullable
public Aggregations getAggregations() {
return aggregations;
}
// endregion
}
@@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.util.CloseableIterator;
import org.springframework.lang.Nullable;
@@ -32,7 +33,7 @@ public interface SearchHitsIterator<T> extends CloseableIterator<SearchHit<T>> {
* @return the aggregations.
*/
@Nullable
AggregationsContainer<?> getAggregations();
Aggregations getAggregations();
/**
* @return the maximum score
@@ -71,11 +71,7 @@ public interface SearchOperations {
* @param clazz the entity class
* @return the suggest response
* @since 4.1
* @deprecated since 4.3 use a {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder} with
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder#withSuggestBuilder(SuggestBuilder)},
* call {@link #search(Query, Class)} and get the suggest from {@link SearchHits#getSuggest()}
*/
@Deprecated
SearchResponse suggest(SuggestBuilder suggestion, Class<?> clazz);
/**
@@ -84,11 +80,7 @@ public interface SearchOperations {
* @param suggestion the query
* @param index the index to run the query against
* @return the suggest response
* @deprecated since 4.3 use a {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder} with
* {@link org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder#withSuggestBuilder(SuggestBuilder)},
* call {@link #search(Query, Class)} and get the suggest from {@link SearchHits#getSuggest()}
*/
@Deprecated
SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
/**
@@ -228,23 +220,4 @@ public interface SearchOperations {
* are completed.
*/
<T> SearchHitsIterator<T> searchForStream(Query query, Class<T> clazz, IndexCoordinates index);
/**
* Creates a {@link Query} to get all documents. Must be implemented by the concrete implementations to provide an
* appropriate query using the respective client.
*
* @return a query to find all documents
* @since 4.3
*/
Query matchAllQuery();
/**
* Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete
* implementations to provide an appropriate query using the respective client.
*
* @param ids the list of ids must not be {@literal null}
* @return query returning the documents with the given ids
* @since 4.3
*/
Query idsQuery(List<String> ids);
}
@@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.client.util.ScrollState;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -54,7 +55,7 @@ abstract class StreamQueries {
Assert.notNull(continueScrollFunction, "continueScrollFunction must not be null.");
Assert.notNull(clearScrollConsumer, "clearScrollConsumer must not be null.");
AggregationsContainer<?> aggregations = searchHits.getAggregations();
Aggregations aggregations = searchHits.getAggregations();
float maxScore = searchHits.getMaxScore();
long totalHits = searchHits.getTotalHits();
TotalHitsRelation totalHitsRelation = searchHits.getTotalHitsRelation();
@@ -77,7 +78,7 @@ abstract class StreamQueries {
@Override
@Nullable
public AggregationsContainer<?> getAggregations() {
public Aggregations getAggregations() {
return aggregations;
}
@@ -1,41 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.clients.elasticsearch7;
import org.elasticsearch.search.aggregations.Aggregation;
import org.springframework.data.elasticsearch.core.AggregationContainer;
import org.springframework.lang.NonNull;
/**
* AggregationContainer implementation for an Elasticsearch7 aggregation.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregation implements AggregationContainer<Aggregation> {
private final Aggregation aggregation;
public ElasticsearchAggregation(Aggregation aggregation) {
this.aggregation = aggregation;
}
@NonNull
@Override
public Aggregation aggregation() {
return aggregation;
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.clients.elasticsearch7;
import org.elasticsearch.search.aggregations.Aggregations;
import org.springframework.data.elasticsearch.core.AggregationsContainer;
import org.springframework.lang.NonNull;
/**
* AggregationsContainer implementation for the Elasticsearch7 aggregations.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
public class ElasticsearchAggregations implements AggregationsContainer<Aggregations> {
private final Aggregations aggregations;
public ElasticsearchAggregations(Aggregations aggregations) {
this.aggregations = aggregations;
}
@NonNull
@Override
public Aggregations aggregations() {
return aggregations;
}
}
@@ -1,6 +0,0 @@
/**
* Classes and interfaces used by the code that uses Elasticsearch 7 client libraries
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.clients.elasticsearch7;
@@ -1,19 +1,4 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.suggest;
package org.springframework.data.elasticsearch.core.completion;
import java.util.List;
import java.util.Map;
@@ -1,3 +1,3 @@
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.elasticsearch.core.suggest.response;
package org.springframework.data.elasticsearch.core.completion;
@@ -1,40 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractPropertyValueConverter implements PropertyValueConverter {
private final PersistentProperty<?> property;
public AbstractPropertyValueConverter(PersistentProperty<?> property) {
Assert.notNull(property, "property must not be null.");
this.property = property;
}
protected PersistentProperty<?> getProperty() {
return property;
}
}
@@ -1,124 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPropertyValueConverter {
protected static final String LT_FIELD = "lt";
protected static final String LTE_FIELD = "lte";
protected static final String GT_FIELD = "gt";
protected static final String GTE_FIELD = "gte";
public AbstractRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}
@Override
public Object read(Object value) {
Assert.notNull(value, "value must not be null.");
Assert.isInstanceOf(Map.class, value, "value must be instance of Map.");
try {
Map<String, Object> source = (Map<String, Object>) value;
Range.Bound<T> lowerBound;
Range.Bound<T> upperBound;
if (source.containsKey(GTE_FIELD)) {
lowerBound = Range.Bound.inclusive(parse((String) source.get(GTE_FIELD)));
} else if (source.containsKey(GT_FIELD)) {
lowerBound = Range.Bound.exclusive(parse((String) source.get(GT_FIELD)));
} else {
lowerBound = Range.Bound.unbounded();
}
if (source.containsKey(LTE_FIELD)) {
upperBound = Range.Bound.inclusive(parse((String) source.get(LTE_FIELD)));
} else if (source.containsKey(LT_FIELD)) {
upperBound = Range.Bound.exclusive(parse((String) source.get(LT_FIELD)));
} else {
upperBound = Range.Bound.unbounded();
}
return Range.of(lowerBound, upperBound);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
@Override
public Object write(Object value) {
Assert.notNull(value, "value must not be null.");
if (!Range.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
Range<T> range = (Range<T>) value;
Range.Bound<T> lowerBound = range.getLowerBound();
Range.Bound<T> upperBound = range.getUpperBound();
Map<String, Object> target = new LinkedHashMap<>();
if (lowerBound.isBounded()) {
String lowerBoundValue = format(lowerBound.getValue().get());
if (lowerBound.isInclusive()) {
target.put(GTE_FIELD, lowerBoundValue);
} else {
target.put(GT_FIELD, lowerBoundValue);
}
}
if (upperBound.isBounded()) {
String upperBoundValue = format(upperBound.getValue().get());
if (upperBound.isInclusive()) {
target.put(LTE_FIELD, upperBoundValue);
} else {
target.put(LT_FIELD, upperBoundValue);
}
}
return target;
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
protected abstract String format(T value);
protected Class<?> getGenericType() {
return getProperty().getTypeInformation().getTypeArguments().get(0).getType();
}
protected abstract T parse(String value);
}
@@ -1,73 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class DatePropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(DatePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DatePropertyValueConverter(PersistentProperty<?> property, List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@Override
public Object read(Object value) {
String s = value.toString();
for (ElasticsearchDateConverter dateConverter : dateConverters) {
try {
return dateConverter.parse(s);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
getProperty().getActualType().getTypeName(), getProperty().getName()));
}
@Override
public Object write(Object value) {
if (!Date.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((Date) value);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
}
@@ -1,62 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class DateRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Date> {
private static final Logger LOGGER = LoggerFactory.getLogger(DateRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DateRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@Override
protected String format(Date value) {
return dateConverters.get(0).format(value);
}
@Override
protected Date parse(String value) {
for (ElasticsearchDateConverter converters : dateConverters) {
try {
return converters.parse(value);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
getGenericType().getTypeName(), getProperty().getName()));
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.convert;
import java.util.Date;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.core.convert.converter.Converter;
/**
* DateTimeConverters
*
* @author Rizwan Idrees
* @author Mohsin Husen
*/
public final class DateTimeConverters {
private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JodaDateTimeConverter implements Converter<ReadableInstant, String> {
INSTANCE;
@Override
public String convert(ReadableInstant source) {
if (source == null) {
return null;
}
return formatter.print(source);
}
}
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JodaLocalDateTimeConverter implements Converter<LocalDateTime, String> {
INSTANCE;
@Override
public String convert(LocalDateTime source) {
if (source == null) {
return null;
}
return formatter.print(source.toDateTime(DateTimeZone.UTC));
}
}
/**
* @deprecated since 4.1
*/
@Deprecated
public enum JavaDateConverter implements Converter<Date, String> {
INSTANCE;
@Override
public String convert(Date source) {
if (source == null) {
return null;
}
return formatter.print(source.getTime());
}
}
}
@@ -19,7 +19,6 @@ import org.springframework.data.convert.EntityConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -94,7 +93,8 @@ public interface ElasticsearchConverter
/**
* Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the
* values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link PropertyValueConverter}. If domainClass is null it's a noop.
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
* domainClass is null it's a noop.
*
* @param query the query that is internally updated, must not be {@literal null}
* @param domainClass the class of the object that is searched with the query
@@ -47,7 +47,7 @@ final public class ElasticsearchDateConverter {
/**
* Creates an ElasticsearchDateConverter for the given {@link DateFormat}.
*
*
* @param dateFormat must not be @{literal null}
* @return converter
*/
@@ -66,6 +66,7 @@ final public class ElasticsearchDateConverter {
*/
public static ElasticsearchDateConverter of(String pattern) {
Assert.notNull(pattern, "pattern must not be null");
Assert.hasText(pattern, "pattern must not be empty");
String[] subPatterns = pattern.split("\\|\\|");
@@ -85,7 +86,7 @@ final public class ElasticsearchDateConverter {
*/
public String format(TemporalAccessor accessor) {
Assert.notNull(accessor, "accessor must not be null");
Assert.notNull("accessor", "accessor must not be null");
if (accessor instanceof Instant) {
Instant instant = (Instant) accessor;
@@ -134,7 +135,7 @@ final public class ElasticsearchDateConverter {
/**
* Creates a {@link DateFormatter} for a given pattern. The pattern can be the name of a {@link DateFormat} enum value
* or a literal pattern.
*
*
* @param pattern the pattern to use
* @return DateFormatter
*/
@@ -171,73 +172,8 @@ final public class ElasticsearchDateConverter {
return new PatternDateFormatter(dateTimeFormatter);
}
@SuppressWarnings("unchecked")
private static <T extends TemporalAccessor> TemporalQuery<T> getTemporalQuery(Class<T> type) {
return temporal -> {
// no reflection for java.time classes (GraalVM native)
if (type == java.time.chrono.HijrahDate.class) {
return (T) java.time.chrono.HijrahDate.from(temporal);
}
if (type == java.time.chrono.JapaneseDate.class) {
return (T) java.time.chrono.JapaneseDate.from(temporal);
}
if (type == java.time.ZonedDateTime.class) {
return (T) java.time.ZonedDateTime.from(temporal);
}
if (type == java.time.LocalDateTime.class) {
return (T) java.time.LocalDateTime.from(temporal);
}
if (type == java.time.chrono.ThaiBuddhistDate.class) {
return (T) java.time.chrono.ThaiBuddhistDate.from(temporal);
}
if (type == java.time.LocalTime.class) {
return (T) java.time.LocalTime.from(temporal);
}
if (type == java.time.ZoneOffset.class) {
return (T) java.time.ZoneOffset.from(temporal);
}
if (type == java.time.OffsetTime.class) {
return (T) java.time.OffsetTime.from(temporal);
}
if (type == java.time.chrono.ChronoLocalDate.class) {
return (T) java.time.chrono.ChronoLocalDate.from(temporal);
}
if (type == java.time.Month.class) {
return (T) java.time.Month.from(temporal);
}
if (type == java.time.chrono.ChronoLocalDateTime.class) {
return (T) java.time.chrono.ChronoLocalDateTime.from(temporal);
}
if (type == java.time.MonthDay.class) {
return (T) java.time.MonthDay.from(temporal);
}
if (type == java.time.Instant.class) {
return (T) java.time.Instant.from(temporal);
}
if (type == java.time.OffsetDateTime.class) {
return (T) java.time.OffsetDateTime.from(temporal);
}
if (type == java.time.chrono.ChronoZonedDateTime.class) {
return (T) java.time.chrono.ChronoZonedDateTime.from(temporal);
}
if (type == java.time.chrono.MinguoDate.class) {
return (T) java.time.chrono.MinguoDate.from(temporal);
}
if (type == java.time.Year.class) {
return (T) java.time.Year.from(temporal);
}
if (type == java.time.DayOfWeek.class) {
return (T) java.time.DayOfWeek.from(temporal);
}
if (type == java.time.LocalDate.class) {
return (T) java.time.LocalDate.from(temporal);
}
if (type == java.time.YearMonth.class) {
return (T) java.time.YearMonth.from(temporal);
}
// for implementations not covered until here use reflection to check for the existence of a static
// from(TemporalAccessor) method
try {
Method method = type.getMethod("from", TemporalAccessor.class);
Object o = method.invoke(null, temporal);
@@ -248,8 +184,7 @@ final public class ElasticsearchDateConverter {
throw new ConversionException("could not create object of class " + type.getName(), e);
}
};
}
// endregion
} // endregion
/**
* a DateFormatter to convert epoch milliseconds
@@ -1,53 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class NumberRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Number> {
public NumberRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}
@Override
protected String format(Number number) {
return String.valueOf(number);
}
@Override
protected Number parse(String value) {
Class<?> type = getGenericType();
if (Integer.class.isAssignableFrom(type)) {
return Integer.valueOf(value);
} else if (Float.class.isAssignableFrom(type)) {
return Float.valueOf(value);
} else if (Long.class.isAssignableFrom(type)) {
return Long.valueOf(value);
} else if (Double.class.isAssignableFrom(type)) {
return Double.valueOf(value);
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
type.getTypeName(), getProperty().getName()));
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 java.time.temporal.TemporalAccessor;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
/**
* @author Sascha Woo
* @since 4.3
*/
public class TemporalPropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalPropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;
}
@SuppressWarnings("unchecked")
@Override
public Object read(Object value) {
String s = value.toString();
Class<?> actualType = getProperty().getActualType();
for (ElasticsearchDateConverter dateConverter : dateConverters) {
try {
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s,
getProperty().getActualType().getTypeName(), getProperty().getName()));
}
@Override
public Object write(Object value) {
if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((TemporalAccessor) value);
} catch (Exception e) {
throw new ConversionException(
String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e);
}
}
}
@@ -1,66 +0,0 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 java.time.temporal.TemporalAccessor;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
/**
* @author Sascha Woo
* @since 4.3
*/
public class TemporalRangePropertyValueConverter extends AbstractRangePropertyValueConverter<TemporalAccessor> {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);
Assert.notEmpty(dateConverters, "dateConverters must not be empty.");
this.dateConverters = dateConverters;
}
@Override
protected String format(TemporalAccessor temporal) {
return dateConverters.get(0).format(temporal);
}
@Override
protected TemporalAccessor parse(String value) {
Class<?> type = getGenericType();
for (ElasticsearchDateConverter converters : dateConverters) {
try {
return converters.parse(value, (Class<? extends TemporalAccessor>) type);
} catch (Exception e) {
LOGGER.trace(e.getMessage(), e);
}
}
throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value,
type.getTypeName(), getProperty().getName()));
}
}

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