mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-08-09 02:53:28 +00:00
Compare commits
126 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0491165f22 | ||
|
b8ea301a1f | ||
|
2b3f1730dc | ||
|
8d984cdd04 | ||
|
ee64017b1a | ||
|
f322caa110 | ||
|
992d86f980 | ||
|
ef52222be8 | ||
|
5a7f7594da | ||
|
eb4c8e02f7 | ||
|
81791dd68d | ||
|
e066c10a30 | ||
|
ea4cc07146 | ||
|
aca34eefeb | ||
|
1348ee3a92 | ||
|
e010257c7d | ||
|
a1aa6f209a | ||
|
a19aa54619 | ||
|
65aa371d64 | ||
|
b3da1786ca | ||
|
9921b46781 | ||
|
aa78e4f026 | ||
|
67cab66062 | ||
|
71f5fa23b7 | ||
|
59075075c5 | ||
|
208824dd9f | ||
|
cff246ba14 | ||
|
fddb0af117 | ||
|
626b274677 | ||
|
979c164135 | ||
|
84a74f5921 | ||
|
58925fca98 | ||
|
fc32e0e57a | ||
|
98ca6a1755 | ||
|
d4962c11fc | ||
|
698684c4bd | ||
|
a67a700fa8 | ||
|
93cf9ab794 | ||
|
73f11a0618 | ||
|
038401f6ce | ||
|
8fcf9e2140 | ||
|
cc87eaf602 | ||
|
a5d0347224 | ||
|
745c69ca39 | ||
|
12365c19cf | ||
|
26a3b324b7 | ||
|
752693399e | ||
|
2cb9e30b61 | ||
|
461d70e173 | ||
|
352e74248a | ||
|
73d52cd686 | ||
|
532bb85d77 | ||
|
f6e17d1af3 | ||
|
62f483cd2c | ||
|
6f8ad00dfa | ||
|
e2fbef632c | ||
|
d131ccafa2 | ||
|
458b05ed37 | ||
|
1c9f3d2f9a | ||
|
2b2bc3e575 | ||
|
5bd0f9cc2d | ||
|
589961022d | ||
|
851525a052 | ||
|
1bc42a9a9a | ||
|
971dcda1f5 | ||
|
93d03830e0 | ||
|
16ce9444da | ||
|
d59c32d29e | ||
|
5713e5bfa6 | ||
|
56a2548156 | ||
|
91ff345cf2 | ||
|
168242fbd0 | ||
|
138760dad7 | ||
|
4bb6cb6f86 | ||
|
09af4f9917 | ||
|
cdd0df7dd4 | ||
|
3fee02c17e | ||
|
a92b57aedd | ||
|
0b6a77be3b | ||
|
9a2b3b1317 | ||
|
171ea62b9b | ||
|
3b2ff95702 | ||
|
51fb872bea | ||
|
67c570606a | ||
|
eb311d089f | ||
|
93492f86da | ||
|
88b2fa970d | ||
|
11e4ebf12c | ||
|
1272fdc188 | ||
|
96eac3ba73 | ||
|
c47d284e6b | ||
|
f5d651b497 | ||
|
d35d38f7d5 | ||
|
2883280b00 | ||
|
fedd97624e | ||
|
9fab24d5ef | ||
|
bf32581105 | ||
|
7f89d79b12 | ||
|
955eb77add | ||
|
0086d35e54 | ||
|
53f0e79990 | ||
|
3da9eaadc5 | ||
|
310f6f0f22 | ||
|
262af9f0e8 | ||
|
bc74126d7e | ||
|
308de7f8db | ||
|
ae5f72fb62 | ||
|
170facc3f3 | ||
|
0aaffebf16 | ||
|
d1da6ac4ed | ||
|
133bc315ed | ||
|
9f243fd2c9 | ||
|
de57159c7f | ||
|
74ed69877d | ||
|
7314bfc21d | ||
|
5909c19ead | ||
|
9df27ef289 | ||
|
28d92359b2 | ||
|
d8b0d526b4 | ||
|
dc832e75a6 | ||
|
866cd96477 | ||
|
162c57df31 | ||
|
3600452796 | ||
|
4b1c4c8000 | ||
|
70d556e526 | ||
|
214d91f3c1 |
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Executable file → Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Executable file → Normal file
Binary file not shown.
3
.mvn/wrapper/maven-wrapper.properties
vendored
Executable file → Normal file
3
.mvn/wrapper/maven-wrapper.properties
vendored
Executable file → Normal file
@ -1 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
|
2
CI.adoc
2
CI.adoc
@ -9,7 +9,7 @@ image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2
|
||||
Since this pipeline is purely Docker-based, it's easy to:
|
||||
|
||||
* Debug what went wrong on your local machine.
|
||||
* Test out a a tweak to your `test.sh` script before sending it out.
|
||||
* Test out a a tweak to your `verify.sh` script before sending it out.
|
||||
* Experiment against a new image before submitting your pull request.
|
||||
|
||||
All of these use cases are great reasons to essentially run what the CI server does on your local machine.
|
||||
|
110
Jenkinsfile
vendored
110
Jenkinsfile
vendored
@ -3,7 +3,7 @@ pipeline {
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS)
|
||||
upstream(upstreamProjects: "spring-data-commons/2.4.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
@ -15,71 +15,86 @@ pipeline {
|
||||
stage("test: baseline (jdk8)") {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'mkdir -p /tmp/jenkins-home'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list verify -Dsort -U -B'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=none ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("Test other configurations") {
|
||||
when {
|
||||
allOf {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
parallel {
|
||||
stage("test: baseline (jdk11)") {
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk11:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'mkdir -p /tmp/jenkins-home'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list verify -Dsort -U -B'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=java11 ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("test: baseline (jdk15)") {
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk15:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
args '-u root -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'mkdir -p /tmp/jenkins-home'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pjava11 clean dependency:list verify -Dsort -U -B'
|
||||
sh 'chown -R 1001:1001 .'
|
||||
script {
|
||||
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
|
||||
docker.image('adoptopenjdk/openjdk15:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=java11 ci/verify.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,16 +103,12 @@ pipeline {
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
@ -106,8 +117,10 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'rm -rf ?'
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' +
|
||||
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,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
@ -117,16 +130,15 @@ pipeline {
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Publish documentation') {
|
||||
when {
|
||||
branch 'master'
|
||||
branch '4.1.x'
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'adoptopenjdk/openjdk8:latest'
|
||||
label 'data'
|
||||
args '-v $HOME:/tmp/jenkins-home'
|
||||
}
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
@ -135,7 +147,10 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' +
|
||||
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} " +
|
||||
@ -144,6 +159,9 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
changed {
|
||||
|
202
LICENSE.txt
Normal file
202
LICENSE.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
6
ci/clean.sh
Executable file
6
ci/clean.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
|
10
ci/verify.sh
Executable file
10
ci/verify.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p /tmp/jenkins-home/.m2/spring-data-elasticsearch
|
||||
chown -R 1001:1001 .
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml \
|
||||
-P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch
|
34
mvnw
vendored
34
mvnw
vendored
@ -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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
# http://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 @@
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
# Maven Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
@ -114,7 +114,6 @@ 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
|
||||
@ -212,7 +211,11 @@ else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
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
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
@ -221,22 +224,38 @@ 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
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
curl -o "$wrapperJarPath" "$jarUrl"
|
||||
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
|
||||
|
||||
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
|
||||
@ -277,6 +296,11 @@ 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" \
|
||||
|
35
mvnw.cmd
vendored
Executable file → Normal file
35
mvnw.cmd
vendored
Executable file → Normal 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 https://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM http://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 Maven2 Start Up Batch script
|
||||
@REM Maven 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 key stroke before ending
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke 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 my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
@ -120,23 +120,44 @@ 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.4.2/maven-wrapper-0.4.2.jar"
|
||||
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||
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
|
||||
)
|
||||
|
||||
@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%
|
||||
)
|
||||
) 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 "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||
)
|
||||
|
||||
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%
|
||||
)
|
||||
)
|
||||
@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
|
||||
|
8
pom.xml
8
pom.xml
@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<version>4.1.15</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<version>2.4.15</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@ -22,8 +22,8 @@
|
||||
<elasticsearch>7.9.3</elasticsearch>
|
||||
<log4j>2.13.3</log4j>
|
||||
<netty>4.1.52.Final</netty>
|
||||
<springdata.commons>2.4.0</springdata.commons>
|
||||
<testcontainers>1.14.3</testcontainers>
|
||||
<springdata.commons>2.4.15</springdata.commons>
|
||||
<testcontainers>1.15.1</testcontainers>
|
||||
<java-module-name>spring.data.elasticsearch</java-module-name>
|
||||
</properties>
|
||||
|
||||
|
29
settings.xml
Normal file
29
settings.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
|
||||
https://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>spring-plugins-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-milestone</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
@ -34,7 +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 Boot
|
||||
| 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.9.3 |2.3.xfootnote:cdv[]
|
||||
| 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.9.3 |2.4.xfootnote:cdv[]
|
||||
| Neumann | 4.0.x | 7.6.2 |2.3.x
|
||||
| Moore | 3.2.x |6.8.12 | 2.2.x
|
||||
| Lovelace | 3.1.x | 6.2.2 |2.1.x
|
||||
|
@ -30,11 +30,15 @@ public class Person implements Persistable<Long> {
|
||||
@Id private Long id;
|
||||
private String lastName;
|
||||
private String firstName;
|
||||
@CreatedDate
|
||||
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
|
||||
private Instant createdDate;
|
||||
@CreatedBy
|
||||
private String createdBy
|
||||
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
|
||||
@LastModifiedDate
|
||||
private Instant lastModifiedDate;
|
||||
@LastModifiedBy
|
||||
private String lastModifiedBy;
|
||||
|
||||
public Long getId() { // <.>
|
||||
|
@ -154,7 +154,7 @@ httpHeaders.add("some-header", "on every request") <1>
|
||||
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.connectedTo("localhost:9200", "localhost:9291") <2>
|
||||
.useSsl() <3>
|
||||
.usingSsl() <3>
|
||||
.withProxy("localhost:8888") <4>
|
||||
.withPathPrefix("ela") <5>
|
||||
.withConnectTimeout(Duration.ofSeconds(5)) <6>
|
||||
|
@ -58,13 +58,14 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu
|
||||
** `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]
|
||||
** `format` and `pattern` definitions for the _Date_ type. `format` must be defined for date types.
|
||||
** `format` and `pattern` definitions for the _Date_ type.
|
||||
** `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.
|
||||
|
||||
NOTE: Properties that derive from `TemporalAccessor` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type. +
|
||||
NOTE: Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` and a
|
||||
format different from `DateFormat.none` or a custom converter must be registered for this type. +
|
||||
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].
|
||||
|
||||
|
@ -20,5 +20,5 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* @since 4.0
|
||||
*/
|
||||
public enum TermVector {
|
||||
none, no, yes, with_positions, with_offsets, with_positions_offsets, with_positions_payloads, with_positions_offets_payloads
|
||||
none, no, yes, with_positions, with_offsets, with_positions_offsets, with_positions_payloads, with_positions_offsets_payloads
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ 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 {
|
||||
@ -104,15 +105,14 @@ public final class RestClients {
|
||||
Duration connectTimeout = clientConfiguration.getConnectTimeout();
|
||||
|
||||
if (!connectTimeout.isNegative()) {
|
||||
|
||||
requestConfigBuilder.setConnectTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(connectTimeout.toMillis()));
|
||||
}
|
||||
|
||||
Duration timeout = clientConfiguration.getSocketTimeout();
|
||||
Duration socketTimeout = clientConfiguration.getSocketTimeout();
|
||||
|
||||
if (!timeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(timeout.toMillis()));
|
||||
if (!socketTimeout.isNegative()) {
|
||||
requestConfigBuilder.setSocketTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
requestConfigBuilder.setConnectionRequestTimeout(Math.toIntExact(socketTimeout.toMillis()));
|
||||
}
|
||||
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
|
@ -145,7 +145,7 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe
|
||||
*/
|
||||
public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices {
|
||||
|
||||
private final HostProvider hostProvider;
|
||||
private final HostProvider<?> hostProvider;
|
||||
private final RequestCreator requestCreator;
|
||||
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
|
||||
|
||||
@ -155,7 +155,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
*
|
||||
* @param hostProvider must not be {@literal null}.
|
||||
*/
|
||||
public DefaultReactiveElasticsearchClient(HostProvider hostProvider) {
|
||||
public DefaultReactiveElasticsearchClient(HostProvider<?> hostProvider) {
|
||||
this(hostProvider, new DefaultRequestCreator());
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
* @param hostProvider must not be {@literal null}.
|
||||
* @param requestCreator must not be {@literal null}.
|
||||
*/
|
||||
public DefaultReactiveElasticsearchClient(HostProvider hostProvider, RequestCreator requestCreator) {
|
||||
public DefaultReactiveElasticsearchClient(HostProvider<?> hostProvider, RequestCreator requestCreator) {
|
||||
|
||||
Assert.notNull(hostProvider, "HostProvider must not be null");
|
||||
Assert.notNull(requestCreator, "RequestCreator must not be null");
|
||||
@ -535,8 +535,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
.flatMap(callback::doWithClient) //
|
||||
.onErrorResume(throwable -> {
|
||||
|
||||
if (throwable instanceof ConnectException) {
|
||||
|
||||
if (isCausedByConnectionException(throwable)) {
|
||||
return hostProvider.getActive(Verification.ACTIVE) //
|
||||
.flatMap(callback::doWithClient);
|
||||
}
|
||||
@ -545,6 +544,27 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the given throwable is a {@link ConnectException} or has one in it's cause chain
|
||||
*
|
||||
* @param throwable the throwable to check
|
||||
* @return true if throwable is caused by a {@link ConnectException}
|
||||
*/
|
||||
private boolean isCausedByConnectionException(Throwable throwable) {
|
||||
|
||||
Throwable t = throwable;
|
||||
do {
|
||||
|
||||
if (t instanceof ConnectException) {
|
||||
return true;
|
||||
}
|
||||
|
||||
t = t.getCause();
|
||||
} while (t != null);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Status> status() {
|
||||
|
||||
@ -823,6 +843,9 @@ 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 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -27,6 +27,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient.Builder;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* Default {@link WebClientProvider} that uses cached {@link WebClient} instances per {@code hostAndPort}.
|
||||
@ -156,7 +157,16 @@ class DefaultWebClientProvider implements WebClientProvider {
|
||||
|
||||
String baseUrl = String.format("%s://%s:%d%s", this.scheme, socketAddress.getHostString(), socketAddress.getPort(),
|
||||
pathPrefix == null ? "" : '/' + pathPrefix);
|
||||
WebClient webClient = builder.baseUrl(baseUrl).filter((request, next) -> next.exchange(request).doOnError(errorListener)).build();
|
||||
|
||||
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
|
||||
// the template will already be encoded by the RequestConverters methods
|
||||
uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
|
||||
builder.uriBuilderFactory(uriBuilderFactory); //
|
||||
|
||||
WebClient webClient = builder //
|
||||
.filter((request, next) -> next.exchange(request) //
|
||||
.doOnError(errorListener)) //
|
||||
.build(); //
|
||||
return webClientConfigurer.apply(webClient);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -34,9 +34,10 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface HostProvider {
|
||||
public interface HostProvider<T extends HostProvider<T>> {
|
||||
|
||||
/**
|
||||
* Create a new {@link HostProvider} best suited for the given {@link WebClientProvider} and number of hosts.
|
||||
@ -46,7 +47,7 @@ public interface HostProvider {
|
||||
* @param endpoints must not be {@literal null} nor empty.
|
||||
* @return new instance of {@link HostProvider}.
|
||||
*/
|
||||
static HostProvider provider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
|
||||
static HostProvider<?> provider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
|
||||
InetSocketAddress... endpoints) {
|
||||
|
||||
Assert.notNull(clientProvider, "WebClientProvider must not be null");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -29,6 +30,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost;
|
||||
import org.springframework.data.elasticsearch.client.ElasticsearchHost.State;
|
||||
import org.springframework.data.elasticsearch.client.NoReachableHostException;
|
||||
@ -42,15 +45,19 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
class MultiNodeHostProvider implements HostProvider {
|
||||
class MultiNodeHostProvider implements HostProvider<MultiNodeHostProvider> {
|
||||
|
||||
private final static Logger LOG = LoggerFactory.getLogger(MultiNodeHostProvider.class);
|
||||
|
||||
private final WebClientProvider clientProvider;
|
||||
private final Supplier<HttpHeaders> headersSupplier;
|
||||
private final Map<InetSocketAddress, ElasticsearchHost> hosts;
|
||||
|
||||
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier, InetSocketAddress... endpoints) {
|
||||
MultiNodeHostProvider(WebClientProvider clientProvider, Supplier<HttpHeaders> headersSupplier,
|
||||
InetSocketAddress... endpoints) {
|
||||
|
||||
this.clientProvider = clientProvider;
|
||||
this.headersSupplier = headersSupplier;
|
||||
@ -58,6 +65,8 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
for (InetSocketAddress endpoint : endpoints) {
|
||||
this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN));
|
||||
}
|
||||
|
||||
LOG.debug("initialized with " + hosts);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -66,7 +75,7 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
*/
|
||||
@Override
|
||||
public Mono<ClusterInformation> clusterInfo() {
|
||||
return nodes(null).map(this::updateNodeState).buffer(hosts.size())
|
||||
return checkNodes(null).map(this::updateNodeState).buffer(hosts.size())
|
||||
.then(Mono.just(new ClusterInformation(new LinkedHashSet<>(this.hosts.values()))));
|
||||
}
|
||||
|
||||
@ -86,14 +95,19 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
@Override
|
||||
public Mono<InetSocketAddress> lookupActiveHost(Verification verification) {
|
||||
|
||||
LOG.trace("lookupActiveHost " + verification + " from " + hosts());
|
||||
|
||||
if (Verification.LAZY.equals(verification)) {
|
||||
for (ElasticsearchHost entry : hosts()) {
|
||||
if (entry.isOnline()) {
|
||||
LOG.trace("lookupActiveHost returning " + entry);
|
||||
return Mono.just(entry.getEndpoint());
|
||||
}
|
||||
}
|
||||
LOG.trace("no online host found with LAZY");
|
||||
}
|
||||
|
||||
LOG.trace("searching for active host");
|
||||
return findActiveHostInKnownActives() //
|
||||
.switchIfEmpty(findActiveHostInUnresolved()) //
|
||||
.switchIfEmpty(findActiveHostInDead()) //
|
||||
@ -105,20 +119,30 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInKnownActives() {
|
||||
return findActiveForSate(State.ONLINE);
|
||||
return findActiveForState(State.ONLINE);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInUnresolved() {
|
||||
return findActiveForSate(State.UNKNOWN);
|
||||
return findActiveForState(State.UNKNOWN);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveHostInDead() {
|
||||
return findActiveForSate(State.OFFLINE);
|
||||
return findActiveForState(State.OFFLINE);
|
||||
}
|
||||
|
||||
private Mono<InetSocketAddress> findActiveForSate(State state) {
|
||||
return nodes(state).map(this::updateNodeState).filter(ElasticsearchHost::isOnline)
|
||||
.map(ElasticsearchHost::getEndpoint).next();
|
||||
private Mono<InetSocketAddress> findActiveForState(State state) {
|
||||
|
||||
LOG.trace("findActiveForState state " + state + ", current hosts: " + hosts);
|
||||
|
||||
return checkNodes(state) //
|
||||
.map(this::updateNodeState) //
|
||||
.filter(ElasticsearchHost::isOnline) //
|
||||
.map(elasticsearchHost -> {
|
||||
LOG.trace("findActiveForState returning host " + elasticsearchHost);
|
||||
return elasticsearchHost;
|
||||
}).map(ElasticsearchHost::getEndpoint) //
|
||||
.takeLast(1) //
|
||||
.next();
|
||||
}
|
||||
|
||||
private ElasticsearchHost updateNodeState(Tuple2<InetSocketAddress, State> tuple2) {
|
||||
@ -129,17 +153,23 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
return elasticsearchHost;
|
||||
}
|
||||
|
||||
private Flux<Tuple2<InetSocketAddress, State>> nodes(@Nullable State state) {
|
||||
private Flux<Tuple2<InetSocketAddress, State>> checkNodes(@Nullable State state) {
|
||||
|
||||
LOG.trace("checkNodes() with state " + state);
|
||||
|
||||
return Flux.fromIterable(hosts()) //
|
||||
.filter(entry -> state == null || entry.getState().equals(state)) //
|
||||
.map(ElasticsearchHost::getEndpoint) //
|
||||
.flatMap(host -> {
|
||||
.concatMap(host -> {
|
||||
|
||||
LOG.trace("checking host " + host);
|
||||
|
||||
Mono<ClientResponse> exchange = createWebClient(host) //
|
||||
.head().uri("/") //
|
||||
.headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) //
|
||||
.exchange().doOnError(throwable -> {
|
||||
.exchange() //
|
||||
.timeout(Duration.ofSeconds(1)) //
|
||||
.doOnError(throwable -> {
|
||||
hosts.put(host, new ElasticsearchHost(host, State.OFFLINE));
|
||||
clientProvider.getErrorListener().accept(throwable);
|
||||
});
|
||||
@ -147,7 +177,10 @@ class MultiNodeHostProvider implements HostProvider {
|
||||
return Mono.just(host).zipWith(exchange
|
||||
.flatMap(it -> it.releaseBody().thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE)));
|
||||
}) //
|
||||
.onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
|
||||
.map(tuple -> {
|
||||
LOG.trace("check result " + tuple);
|
||||
return tuple;
|
||||
}).onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable));
|
||||
}
|
||||
|
||||
private List<ElasticsearchHost> hosts() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -32,9 +32,10 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
class SingleNodeHostProvider implements HostProvider {
|
||||
class SingleNodeHostProvider implements HostProvider<SingleNodeHostProvider> {
|
||||
|
||||
private final WebClientProvider clientProvider;
|
||||
private final Supplier<HttpHeaders> headersSupplier;
|
||||
|
@ -20,6 +20,12 @@ 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;
|
||||
@ -44,6 +50,8 @@ 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;
|
||||
@ -64,7 +72,11 @@ 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.ParsedSignificantLongTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.ParsedSignificantStringTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.SignificantLongTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.SignificantStringTerms;
|
||||
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
|
||||
import org.elasticsearch.search.aggregations.metrics.*;
|
||||
import org.elasticsearch.search.aggregations.pipeline.*;
|
||||
@ -81,7 +93,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.
|
||||
* License, Version 2.0. The latest version used from Elasticsearch is 7.10.2.
|
||||
* </p>
|
||||
* Modified for usage with {@link ReactiveElasticsearchClient}.
|
||||
* <p>
|
||||
@ -126,6 +138,8 @@ 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));
|
||||
@ -142,10 +156,15 @@ 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());
|
||||
|
@ -1319,7 +1319,8 @@ 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 `:`
|
||||
URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null);
|
||||
// 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);
|
||||
// manually encode any slash that each part may contain
|
||||
return uri.getRawPath().substring(1).replaceAll("/", "%2F");
|
||||
} catch (URISyntaxException e) {
|
||||
|
@ -31,20 +31,25 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
|
||||
|
||||
/**
|
||||
* Return the {@link RestHighLevelClient} instance used to connect to the cluster. <br />
|
||||
* Annotate with {@link Bean} in case you want to expose a {@link RestHighLevelClient} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public abstract RestHighLevelClient elasticsearchClient();
|
||||
|
||||
/**
|
||||
* Creates {@link ElasticsearchOperations}.
|
||||
* Creates {@link ElasticsearchOperations}. <br/>
|
||||
* NOTE: in version 4.1.2 the second parameter was added, previously this implementation called
|
||||
* {@link #elasticsearchClient()} directly. This is not possible anymore, as the base configuration classes don not
|
||||
* use proxied bean methods anymore.
|
||||
*
|
||||
* @param elasticsearchConverter the {@link ElasticsearchConverter} to use*
|
||||
* @param elasticsearchClient the {@link RestHighLevelClient} to use
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter);
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
RestHighLevelClient elasticsearchClient) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package org.springframework.data.elasticsearch.config;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
|
||||
@ -31,16 +30,14 @@ import org.springframework.lang.Nullable;
|
||||
* @since 3.2
|
||||
* @see ElasticsearchConfigurationSupport
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class AbstractReactiveElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
* Return the {@link ReactiveElasticsearchClient} instance used to connect to the cluster. <br />
|
||||
* Annotate with {@link Bean} in case you want to expose a {@link ReactiveElasticsearchClient} instance to the
|
||||
* {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public abstract ReactiveElasticsearchClient reactiveElasticsearchClient();
|
||||
|
||||
/**
|
||||
@ -49,9 +46,10 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) {
|
||||
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter,
|
||||
ReactiveElasticsearchClient reactiveElasticsearchClient) {
|
||||
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
|
||||
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient,
|
||||
elasticsearchConverter);
|
||||
template.setIndicesOptions(indicesOptions());
|
||||
template.setRefreshPolicy(refreshPolicy());
|
||||
|
@ -40,15 +40,16 @@ import org.springframework.util.StringUtils;
|
||||
* @author Peter-Josef Meisch
|
||||
* @since 3.2
|
||||
*/
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ElasticsearchConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public ElasticsearchConverter elasticsearchEntityMapper(
|
||||
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
|
||||
SimpleElasticsearchMappingContext elasticsearchMappingContext, ElasticsearchCustomConversions elasticsearchCustomConversions) {
|
||||
|
||||
MappingElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
|
||||
elasticsearchMappingContext);
|
||||
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
|
||||
elasticsearchConverter.setConversions(elasticsearchCustomConversions);
|
||||
return elasticsearchConverter;
|
||||
}
|
||||
|
||||
@ -60,11 +61,11 @@ public class ElasticsearchConfigurationSupport {
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
|
||||
public SimpleElasticsearchMappingContext elasticsearchMappingContext(ElasticsearchCustomConversions elasticsearchCustomConversions) {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
|
||||
mappingContext.setInitialEntitySet(getInitialEntitySet());
|
||||
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions().getSimpleTypeHolder());
|
||||
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
@ -147,13 +147,14 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(entity, "entity must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
IndexQuery query = getIndexQuery(entity);
|
||||
index(query, index);
|
||||
T entityAfterBeforeConvert = maybeCallbackBeforeConvert(entity, index);
|
||||
|
||||
// suppressing because it's either entity itself or something of a correct type returned by an entity callback
|
||||
@SuppressWarnings("unchecked")
|
||||
T castResult = (T) query.getObject();
|
||||
return castResult;
|
||||
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
|
||||
doIndex(query, index);
|
||||
|
||||
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
|
||||
|
||||
return entityAfterAfterSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -192,6 +193,20 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return save(Arrays.asList(entities));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
|
||||
String documentId = doIndex(query, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public abstract String doIndex(IndexQuery query, IndexCoordinates indexCoordinates);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(String id, Class<T> clazz) {
|
||||
@ -261,11 +276,38 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return bulkIndex(queries, bulkOptions, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
return bulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, Class<?> clazz) {
|
||||
bulkUpdate(queries, getIndexCoordinatesFor(clazz));
|
||||
}
|
||||
|
||||
public List<IndexedObjectInformation> bulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
|
||||
List<IndexedObjectInformation> indexedObjectInformations = doBulkOperation(queries, bulkOptions, index);
|
||||
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
|
||||
return indexedObjectInformations;
|
||||
}
|
||||
|
||||
public abstract List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index);
|
||||
// endregion
|
||||
|
||||
// region SearchOperations
|
||||
@ -310,7 +352,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
|
||||
|
||||
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
|
||||
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index);
|
||||
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -620,6 +662,20 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
if (queryObject != null) {
|
||||
queryObject = maybeCallbackBeforeConvert(queryObject, index);
|
||||
indexQuery.setObject(queryObject);
|
||||
// the callback might have set som values relevant for the IndexQuery
|
||||
IndexQuery newQuery = getIndexQuery(queryObject);
|
||||
|
||||
if (indexQuery.getRouting() == null && newQuery.getRouting() != null) {
|
||||
indexQuery.setRouting(newQuery.getRouting());
|
||||
}
|
||||
|
||||
if (indexQuery.getSeqNo() == null && newQuery.getSeqNo() != null) {
|
||||
indexQuery.setSeqNo(newQuery.getSeqNo());
|
||||
}
|
||||
|
||||
if (indexQuery.getPrimaryTerm() == null && newQuery.getPrimaryTerm() != null) {
|
||||
indexQuery.setPrimaryTerm(newQuery.getPrimaryTerm());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -34,11 +35,13 @@ import org.elasticsearch.client.indices.GetIndexRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesRequest;
|
||||
import org.elasticsearch.client.indices.GetIndexTemplatesResponse;
|
||||
import org.elasticsearch.client.indices.GetMappingsRequest;
|
||||
import org.elasticsearch.client.indices.GetMappingsResponse;
|
||||
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;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasActions;
|
||||
import org.springframework.data.elasticsearch.core.index.AliasData;
|
||||
@ -61,6 +64,8 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class);
|
||||
|
||||
private final ElasticsearchRestTemplate restTemplate;
|
||||
|
||||
public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class<?> boundClass) {
|
||||
@ -117,10 +122,19 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(index);
|
||||
|
||||
return restTemplate.execute(client -> {
|
||||
GetMappingsResponse mapping = client.indices().getMapping(mappingsRequest, RequestOptions.DEFAULT);
|
||||
// we only return data for the first index name that was requested (always have done so)
|
||||
String index1 = mappingsRequest.indices()[0];
|
||||
return mapping.mappings().get(index1).getSourceAsMap();
|
||||
Map<String, MappingMetadata> mappings = client.indices() //
|
||||
.getMapping(mappingsRequest, RequestOptions.DEFAULT) //
|
||||
.mappings(); //
|
||||
|
||||
if (mappings == null || mappings.size() == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (mappings.size() > 1) {
|
||||
LOGGER.warn("more than one mapping returned for " + index.getIndexName());
|
||||
}
|
||||
// we have at least one, take the first from the iterator
|
||||
return mappings.entrySet().iterator().next().getValue().getSourceAsMap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -40,6 +41,7 @@ import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateReque
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.elasticsearch.cluster.metadata.IndexTemplateMetadata;
|
||||
import org.elasticsearch.cluster.metadata.MappingMetadata;
|
||||
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
||||
import org.elasticsearch.common.compress.CompressedXContent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
@ -126,10 +128,19 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp
|
||||
|
||||
GetMappingsRequest mappingsRequest = requestFactory.getMappingsRequest(client, index);
|
||||
|
||||
return client.admin().indices().getMappings( //
|
||||
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetadata>> mappings = client.admin().indices().getMappings( //
|
||||
mappingsRequest).actionGet() //
|
||||
.getMappings().get(mappingsRequest.indices()[0]).get(IndexCoordinates.TYPE) //
|
||||
.getSourceAsMap();
|
||||
.getMappings();
|
||||
|
||||
if (mappings == null || mappings.size() == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (mappings.size() > 1) {
|
||||
LOGGER.warn("more than one mapping returned for " + index.getIndexName());
|
||||
}
|
||||
// we have at least one, take the first from the iterator
|
||||
return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -137,10 +137,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
public String doIndex(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
IndexRequest request = requestFactory.indexRequest(query, index);
|
||||
IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT));
|
||||
@ -152,8 +149,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return indexResponse.getId();
|
||||
}
|
||||
|
||||
@ -187,16 +182,6 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return execute(client -> client.get(request, RequestOptions.DEFAULT).isExists());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
return doBulkOperation(queries, bulkOptions, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
@ -237,14 +222,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
BulkRequest bulkRequest = requestFactory.bulkRequest(queries, bulkOptions, index);
|
||||
List<IndexedObjectInformation> indexedObjectInformationList = checkForBulkOperationFailure(
|
||||
execute(client -> client.bulk(bulkRequest, RequestOptions.DEFAULT)));
|
||||
updateIndexedObjectsWithQueries(queries, indexedObjectInformationList);
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
return indexedObjectInformationList;
|
||||
}
|
||||
// endregion
|
||||
|
@ -144,10 +144,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
// endregion
|
||||
|
||||
// region DocumentOperations
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
maybeCallbackBeforeConvertWithQuery(query, index);
|
||||
public String doIndex(IndexQuery query, IndexCoordinates index) {
|
||||
|
||||
IndexRequestBuilder indexRequestBuilder = requestFactory.indexRequestBuilder(client, query, index);
|
||||
ActionFuture<IndexResponse> future = indexRequestBuilder.execute();
|
||||
@ -166,8 +163,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
response.getPrimaryTerm(), response.getVersion()));
|
||||
}
|
||||
|
||||
maybeCallbackAfterSaveWithQuery(query, index);
|
||||
|
||||
return documentId;
|
||||
}
|
||||
|
||||
@ -201,22 +196,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return getRequestBuilder.execute().actionGet().isExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkIndex(List<IndexQuery> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
|
||||
Assert.notNull(queries, "List of IndexQuery must not be null");
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
|
||||
List<IndexedObjectInformation> indexedObjectInformations = doBulkOperation(queries, bulkOptions, index);
|
||||
|
||||
updateIndexedObjectsWithQueries(queries, indexedObjectInformations);
|
||||
|
||||
maybeCallbackAfterSaveWithQueries(queries, index);
|
||||
|
||||
return indexedObjectInformations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bulkUpdate(List<UpdateQuery> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
|
||||
@ -261,11 +240,13 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
return new UpdateResponse(result);
|
||||
}
|
||||
|
||||
private List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
public List<IndexedObjectInformation> doBulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
maybeCallbackBeforeConvertWithQueries(queries, index);
|
||||
BulkRequestBuilder bulkRequest = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index);
|
||||
return checkForBulkOperationFailure(bulkRequest.execute().actionGet());
|
||||
final List<IndexedObjectInformation> indexedObjectInformations = checkForBulkOperationFailure(
|
||||
bulkRequest.execute().actionGet());
|
||||
updateIndexedObjectsWithQueries(queries, indexedObjectInformations);
|
||||
return indexedObjectInformations;
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -54,6 +54,7 @@ import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchRequestBuilder;
|
||||
import org.elasticsearch.action.support.ActiveShardCount;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.action.update.UpdateRequestBuilder;
|
||||
import org.elasticsearch.client.Client;
|
||||
@ -85,6 +86,7 @@ import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
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;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
|
||||
@ -111,6 +113,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.*;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@ -860,9 +863,7 @@ class RequestFactory {
|
||||
elasticsearchConverter.updateQuery(searchQuery, clazz);
|
||||
List<MultiGetRequest.Item> items = new ArrayList<>();
|
||||
|
||||
if (!isEmpty(searchQuery.getFields())) {
|
||||
searchQuery.addSourceFilter(new FetchSourceFilter(toArray(searchQuery.getFields()), null));
|
||||
}
|
||||
FetchSourceContext fetchSourceContext = getFetchSourceContext(searchQuery);
|
||||
|
||||
if (!isEmpty(searchQuery.getIds())) {
|
||||
String indexName = index.getIndexName();
|
||||
@ -872,6 +873,11 @@ class RequestFactory {
|
||||
if (searchQuery.getRoute() != null) {
|
||||
item = item.routing(searchQuery.getRoute());
|
||||
}
|
||||
|
||||
if (fetchSourceContext != null) {
|
||||
item.fetchSourceContext(fetchSourceContext);
|
||||
}
|
||||
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
@ -886,15 +892,17 @@ class RequestFactory {
|
||||
String indexName = index.getIndexName();
|
||||
IndexRequest indexRequest;
|
||||
|
||||
if (query.getObject() != null) {
|
||||
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
|
||||
Object queryObject = query.getObject();
|
||||
|
||||
if (queryObject != null) {
|
||||
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : 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(query.getObject()).toJson(), Requests.INDEX_CONTENT_TYPE);
|
||||
indexRequest.source(elasticsearchConverter.mapObject(queryObject).toJson(), Requests.INDEX_CONTENT_TYPE);
|
||||
} else if (query.getSource() != null) {
|
||||
indexRequest = new IndexRequest(indexName).id(query.getId()).source(query.getSource(),
|
||||
Requests.INDEX_CONTENT_TYPE);
|
||||
@ -905,7 +913,8 @@ class RequestFactory {
|
||||
|
||||
if (query.getVersion() != null) {
|
||||
indexRequest.version(query.getVersion());
|
||||
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
|
||||
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
|
||||
queryObject != null ? queryObject.getClass() : null);
|
||||
indexRequest.versionType(versionType);
|
||||
}
|
||||
|
||||
@ -930,15 +939,16 @@ class RequestFactory {
|
||||
|
||||
IndexRequestBuilder indexRequestBuilder;
|
||||
|
||||
if (query.getObject() != null) {
|
||||
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
|
||||
Object queryObject = query.getObject();
|
||||
if (queryObject != null) {
|
||||
String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : 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(query.getObject()).toJson(),
|
||||
indexRequestBuilder.setSource(elasticsearchConverter.mapObject(queryObject).toJson(),
|
||||
Requests.INDEX_CONTENT_TYPE);
|
||||
} else if (query.getSource() != null) {
|
||||
indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(),
|
||||
@ -949,7 +959,8 @@ class RequestFactory {
|
||||
}
|
||||
if (query.getVersion() != null) {
|
||||
indexRequestBuilder.setVersion(query.getVersion());
|
||||
VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass());
|
||||
VersionType versionType = retrieveVersionTypeFromPersistentEntity(
|
||||
queryObject != null ? queryObject.getClass() : null);
|
||||
indexRequestBuilder.setVersionType(versionType);
|
||||
}
|
||||
|
||||
@ -1162,6 +1173,10 @@ class RequestFactory {
|
||||
request.routing(query.getRoute());
|
||||
}
|
||||
|
||||
if (query.getScrollTime() != null) {
|
||||
request.scroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
|
||||
}
|
||||
|
||||
request.source(sourceBuilder);
|
||||
return request;
|
||||
}
|
||||
@ -1237,6 +1252,10 @@ class RequestFactory {
|
||||
searchRequestBuilder.setRouting(query.getRoute());
|
||||
}
|
||||
|
||||
if (query.getScrollTime() != null) {
|
||||
searchRequestBuilder.setScroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis()));
|
||||
}
|
||||
|
||||
return searchRequestBuilder;
|
||||
}
|
||||
|
||||
@ -1553,6 +1572,85 @@ class RequestFactory {
|
||||
return elasticsearchFilter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ElasticsearchPersistentEntity<?> getPersistentEntity(@Nullable Class<?> clazz) {
|
||||
return clazz != null ? elasticsearchConverter.getMappingContext().getPersistentEntity(clazz) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getPersistentEntityId(Object entity) {
|
||||
|
||||
Object identifier = elasticsearchConverter.getMappingContext() //
|
||||
.getRequiredPersistentEntity(entity.getClass()) //
|
||||
.getIdentifierAccessor(entity).getIdentifier();
|
||||
|
||||
if (identifier != null) {
|
||||
return identifier.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class<?> clazz) {
|
||||
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = elasticsearchConverter
|
||||
.getMappingContext();
|
||||
|
||||
ElasticsearchPersistentEntity<?> persistentEntity = clazz != null ? mappingContext.getPersistentEntity(clazz)
|
||||
: null;
|
||||
|
||||
VersionType versionType = null;
|
||||
|
||||
if (persistentEntity != null) {
|
||||
versionType = persistentEntity.getVersionType();
|
||||
}
|
||||
|
||||
return versionType != null ? versionType : VersionType.EXTERNAL;
|
||||
}
|
||||
|
||||
private String[] toArray(List<String> values) {
|
||||
String[] valuesAsArray = new String[values.size()];
|
||||
return values.toArray(valuesAsArray);
|
||||
}
|
||||
// endregion
|
||||
|
||||
private boolean hasSeqNoPrimaryTermProperty(@Nullable Class<?> entityClass) {
|
||||
|
||||
if (entityClass == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!elasticsearchConverter.getMappingContext().hasPersistentEntityFor(entityClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
|
||||
.getRequiredPersistentEntity(entityClass);
|
||||
return entity.hasSeqNoPrimaryTermProperty();
|
||||
}
|
||||
|
||||
private FetchSourceContext getFetchSourceContext(Query searchQuery) {
|
||||
FetchSourceContext fetchSourceContext = null;
|
||||
SourceFilter sourceFilter = searchQuery.getSourceFilter();
|
||||
|
||||
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 fetchSourceContext;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region response stuff
|
||||
|
||||
/**
|
||||
@ -1582,57 +1680,6 @@ class RequestFactory {
|
||||
|
||||
return settings;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region helper functions
|
||||
@Nullable
|
||||
private ElasticsearchPersistentEntity<?> getPersistentEntity(@Nullable Class<?> clazz) {
|
||||
return clazz != null ? elasticsearchConverter.getMappingContext().getPersistentEntity(clazz) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getPersistentEntityId(Object entity) {
|
||||
|
||||
Object identifier = elasticsearchConverter.getMappingContext() //
|
||||
.getRequiredPersistentEntity(entity.getClass()) //
|
||||
.getIdentifierAccessor(entity).getIdentifier();
|
||||
|
||||
if (identifier != null) {
|
||||
return identifier.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private VersionType retrieveVersionTypeFromPersistentEntity(Class<?> clazz) {
|
||||
|
||||
VersionType versionType = elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz)
|
||||
.getVersionType();
|
||||
|
||||
return versionType != null ? versionType : VersionType.EXTERNAL;
|
||||
}
|
||||
|
||||
private String[] toArray(List<String> values) {
|
||||
String[] valuesAsArray = new String[values.size()];
|
||||
return values.toArray(valuesAsArray);
|
||||
}
|
||||
// endregion
|
||||
|
||||
private boolean hasSeqNoPrimaryTermProperty(@Nullable Class<?> entityClass) {
|
||||
|
||||
if (entityClass == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!elasticsearchConverter.getMappingContext().hasPersistentEntityFor(entityClass)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
|
||||
.getRequiredPersistentEntity(entityClass);
|
||||
return entity.hasSeqNoPrimaryTermProperty();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
@ -86,6 +86,12 @@ public final class SearchHitSupport {
|
||||
return unwrapSearchHitsIterator((SearchHitsIterator<?>) result);
|
||||
}
|
||||
|
||||
if (result instanceof SearchPage<?>) {
|
||||
SearchPage<?> searchPage = (SearchPage<?>) result;
|
||||
List<?> content = (List<?>) SearchHitSupport.unwrapSearchHits(searchPage.getSearchHits());
|
||||
return new PageImpl<>(content, searchPage.getPageable(), searchPage.getTotalElements());
|
||||
}
|
||||
|
||||
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
|
||||
|
||||
if (result instanceof Flux) {
|
||||
|
@ -172,19 +172,19 @@ public class GeoConverters {
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
|
||||
switch (type) {
|
||||
case GeoJsonPoint.TYPE:
|
||||
case "point":
|
||||
return MapToGeoJsonPointConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiPoint.TYPE:
|
||||
case "multipoint":
|
||||
return MapToGeoJsonMultiPointConverter.INSTANCE.convert(source);
|
||||
case GeoJsonLineString.TYPE:
|
||||
case "linestring":
|
||||
return MapToGeoJsonLineStringConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiLineString.TYPE:
|
||||
case "multilinestring":
|
||||
return MapToGeoJsonMultiLineStringConverter.INSTANCE.convert(source);
|
||||
case GeoJsonPolygon.TYPE:
|
||||
case "polygon":
|
||||
return MapToGeoJsonPolygonConverter.INSTANCE.convert(source);
|
||||
case GeoJsonMultiPolygon.TYPE:
|
||||
case "multipolygon":
|
||||
return MapToGeoJsonMultiPolygonConverter.INSTANCE.convert(source);
|
||||
case GeoJsonGeometryCollection.TYPE:
|
||||
case "geometrycollection":
|
||||
return MapToGeoJsonGeometryCollectionConverter.INSTANCE.convert(source);
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown GeoJson type " + type);
|
||||
@ -217,7 +217,7 @@ public class GeoConverters {
|
||||
public GeoJsonPoint convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonPoint.TYPE), "does not contain a type 'Point'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonPoint.TYPE), "does not contain a type 'Point'");
|
||||
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
@ -255,7 +255,7 @@ public class GeoConverters {
|
||||
public GeoJsonMultiPoint convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiPoint.TYPE), "does not contain a type 'MultiPoint'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonMultiPoint.TYPE), "does not contain a type 'MultiPoint'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
@ -290,7 +290,7 @@ public class GeoConverters {
|
||||
public GeoJsonLineString convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonLineString.TYPE), "does not contain a type 'LineString'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonLineString.TYPE), "does not contain a type 'LineString'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
@ -322,7 +322,7 @@ public class GeoConverters {
|
||||
public GeoJsonMultiLineString convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiLineString.TYPE), "does not contain a type 'MultiLineString'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonMultiLineString.TYPE), "does not contain a type 'MultiLineString'");
|
||||
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
|
||||
return GeoJsonMultiLineString.of(lines);
|
||||
}
|
||||
@ -350,7 +350,7 @@ public class GeoConverters {
|
||||
public GeoJsonPolygon convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonPolygon.TYPE), "does not contain a type 'Polygon'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonPolygon.TYPE), "does not contain a type 'Polygon'");
|
||||
List<GeoJsonLineString> lines = geoJsonLineStringsFromMap(source);
|
||||
Assert.isTrue(lines.size() > 0, "no linestrings defined in polygon");
|
||||
GeoJsonPolygon geoJsonPolygon = GeoJsonPolygon.of(lines.get(0));
|
||||
@ -394,7 +394,7 @@ public class GeoConverters {
|
||||
public GeoJsonMultiPolygon convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonMultiPolygon.TYPE), "does not contain a type 'MultiPolygon'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonMultiPolygon.TYPE), "does not contain a type 'MultiPolygon'");
|
||||
Object coordinates = source.get("coordinates");
|
||||
Assert.notNull(coordinates, "Document to convert does not contain coordinates");
|
||||
Assert.isTrue(coordinates instanceof List, "coordinates must be a List");
|
||||
@ -441,7 +441,8 @@ public class GeoConverters {
|
||||
public GeoJsonGeometryCollection convert(Map<String, Object> source) {
|
||||
|
||||
String type = GeoConverters.getGeoJsonType(source);
|
||||
Assert.isTrue(type.equals(GeoJsonGeometryCollection.TYPE), "does not contain a type 'GeometryCollection'");
|
||||
Assert.isTrue(type.equalsIgnoreCase(GeoJsonGeometryCollection.TYPE),
|
||||
"does not contain a type 'GeometryCollection'");
|
||||
Object geometries = source.get("geometries");
|
||||
Assert.notNull(geometries, "Document to convert does not contain geometries");
|
||||
Assert.isTrue(geometries instanceof List, "geometries must be a List");
|
||||
@ -461,7 +462,7 @@ public class GeoConverters {
|
||||
Assert.notNull(type, "Document to convert does not contain a type");
|
||||
Assert.isTrue(type instanceof String, "type must be a String");
|
||||
|
||||
return type.toString();
|
||||
return type.toString().toLowerCase();
|
||||
}
|
||||
|
||||
private static List<Double> toCoordinates(Point point) {
|
||||
|
@ -216,6 +216,7 @@ public class MappingBuilder {
|
||||
Field fieldAnnotation = property.findAnnotation(Field.class);
|
||||
boolean isCompletionProperty = property.isCompletionProperty();
|
||||
boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property);
|
||||
DynamicMapping dynamicMapping = property.findAnnotation(DynamicMapping.class);
|
||||
|
||||
if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) {
|
||||
|
||||
@ -230,7 +231,7 @@ public class MappingBuilder {
|
||||
: null;
|
||||
|
||||
mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
|
||||
fieldAnnotation, property.findAnnotation(DynamicMapping.class));
|
||||
fieldAnnotation, dynamicMapping);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -245,9 +246,9 @@ public class MappingBuilder {
|
||||
if (isRootObject && fieldAnnotation != null && property.isIdProperty()) {
|
||||
applyDefaultIdFieldMapping(builder, property);
|
||||
} else if (multiField != null) {
|
||||
addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty);
|
||||
addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty, dynamicMapping);
|
||||
} else if (fieldAnnotation != null) {
|
||||
addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty);
|
||||
addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping);
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,7 +329,7 @@ public class MappingBuilder {
|
||||
* @throws IOException
|
||||
*/
|
||||
private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
|
||||
Field annotation, boolean nestedOrObjectField) throws IOException {
|
||||
Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException {
|
||||
|
||||
// build the property json, if empty skip it as this is no valid mapping
|
||||
XContentBuilder propertyBuilder = jsonBuilder().startObject();
|
||||
@ -340,6 +341,11 @@ public class MappingBuilder {
|
||||
}
|
||||
|
||||
builder.startObject(property.getFieldName());
|
||||
|
||||
if (nestedOrObjectField && dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
}
|
||||
|
||||
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
|
||||
builder.endObject();
|
||||
}
|
||||
@ -380,10 +386,15 @@ public class MappingBuilder {
|
||||
* @throws IOException
|
||||
*/
|
||||
private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property,
|
||||
MultiField annotation, boolean nestedOrObjectField) throws IOException {
|
||||
MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException {
|
||||
|
||||
// main field
|
||||
builder.startObject(property.getFieldName());
|
||||
|
||||
if (nestedOrObjectField && dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
}
|
||||
|
||||
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);
|
||||
|
||||
// inner fields
|
||||
|
@ -15,10 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.join;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Subhobrata Dey
|
||||
* @author Sascha Woo
|
||||
* @since 4.1
|
||||
*/
|
||||
public class JoinField<ID> {
|
||||
@ -35,6 +39,7 @@ public class JoinField<ID> {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
public JoinField(String name, @Nullable ID parent) {
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
@ -52,4 +57,21 @@ public class JoinField<ID> {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof JoinField)) {
|
||||
return false;
|
||||
}
|
||||
JoinField other = (JoinField) obj;
|
||||
return Objects.equals(name, other.name) && Objects.equals(parent, other.parent);
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,11 @@ import org.springframework.data.util.TypeInformation;
|
||||
public class SimpleElasticsearchMappingContext
|
||||
extends AbstractMappingContext<SimpleElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> {
|
||||
|
||||
@Override
|
||||
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
|
||||
return !ElasticsearchSimpleTypes.HOLDER.isSimpleType(type.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SimpleElasticsearchPersistentEntity<?> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
return new SimpleElasticsearchPersistentEntity<>(typeInformation);
|
||||
|
@ -31,6 +31,9 @@ import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
@ -74,7 +77,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
private @Nullable VersionType versionType;
|
||||
private boolean createIndexAndMapping;
|
||||
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
|
||||
|
||||
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;;
|
||||
private final ConcurrentHashMap<String, Expression> indexNameExpressions = new ConcurrentHashMap<>();
|
||||
private final Lazy<EvaluationContext> indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext);
|
||||
|
||||
public SimpleElasticsearchPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
|
||||
@ -302,12 +308,20 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
return seqNoPrimaryTermProperty;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ElasticsearchPersistentProperty getJoinFieldProperty() {
|
||||
return joinFieldProperty;
|
||||
}
|
||||
|
||||
// region SpEL handling
|
||||
|
||||
@Override
|
||||
public void setEvaluationContextProvider(EvaluationContextProvider provider) {
|
||||
super.setEvaluationContextProvider(provider);
|
||||
this.evaluationContextProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolves all the names in the IndexCoordinates object. If a name cannot be resolved, the original name is returned.
|
||||
*
|
||||
@ -316,14 +330,12 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
*/
|
||||
private IndexCoordinates resolve(IndexCoordinates indexCoordinates) {
|
||||
|
||||
EvaluationContext context = getEvaluationContext(null);
|
||||
|
||||
String[] indexNames = indexCoordinates.getIndexNames();
|
||||
String[] resolvedNames = new String[indexNames.length];
|
||||
|
||||
for (int i = 0; i < indexNames.length; i++) {
|
||||
String indexName = indexNames[i];
|
||||
resolvedNames[i] = resolve(context, indexName);
|
||||
resolvedNames[i] = resolve(indexName);
|
||||
}
|
||||
|
||||
return IndexCoordinates.of(resolvedNames);
|
||||
@ -332,22 +344,49 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
/**
|
||||
* tries to resolve the given name. If this is not successful, the original value is returned
|
||||
*
|
||||
* @param context SpEL evaluation context
|
||||
* @param name name to resolve
|
||||
* @return the resolved name or the input name if it cannot be resolved
|
||||
*/
|
||||
private String resolve(EvaluationContext context, String name) {
|
||||
private String resolve(String name) {
|
||||
|
||||
Assert.notNull(name, "name must not be null");
|
||||
|
||||
Expression expression = indexNameExpressions.computeIfAbsent(name, s -> {
|
||||
Expression expr = PARSER.parseExpression(name, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expr instanceof LiteralExpression ? null : expr;
|
||||
});
|
||||
Expression expression = getExpressionForIndexName(name);
|
||||
|
||||
String resolvedName = expression != null ? expression.getValue(context, String.class) : null;
|
||||
String resolvedName = expression != null ? expression.getValue(indexNameEvaluationContext.get(), String.class) : null;
|
||||
return resolvedName != null ? resolvedName : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an {@link Expression} for #name if name contains a {@link ParserContext#TEMPLATE_EXPRESSION} otherwise
|
||||
* returns {@literal null}.
|
||||
*
|
||||
* @param name the name to get the expression for
|
||||
* @return Expression may be null
|
||||
*/
|
||||
@Nullable
|
||||
private Expression getExpressionForIndexName(String name) {
|
||||
return indexNameExpressions.computeIfAbsent(name, s -> {
|
||||
Expression expr = PARSER.parseExpression(s, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expr instanceof LiteralExpression ? null : expr;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* build the {@link EvaluationContext} considering {@link ExpressionDependencies} from the name returned by
|
||||
* {@link #getIndexName()}.
|
||||
*
|
||||
* @return EvaluationContext
|
||||
*/
|
||||
private EvaluationContext getIndexNameEvaluationContext() {
|
||||
|
||||
Expression expression = getExpressionForIndexName(getIndexName());
|
||||
ExpressionDependencies expressionDependencies = expression != null ? ExpressionDependencies.discover(expression)
|
||||
: ExpressionDependencies.none();
|
||||
|
||||
return evaluationContextProvider.getEvaluationContext(null, expressionDependencies);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@Override
|
||||
@ -363,5 +402,4 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
.put("index.refresh_interval", getRefreshInterval()).put("index.store.type", getIndexStoreType()).map();
|
||||
return Document.from(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -163,10 +163,13 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
&& (isTemporalAccessor || isDate)) {
|
||||
DateFormat dateFormat = field.format();
|
||||
|
||||
String property = getOwner().getType().getSimpleName() + "." + getName();
|
||||
|
||||
if (dateFormat == DateFormat.none) {
|
||||
throw new MappingException(
|
||||
String.format("Property %s is annotated with FieldType.%s but has no DateFormat defined",
|
||||
getOwner().getType().getSimpleName() + "." + getName(), field.type().name()));
|
||||
LOGGER.warn(
|
||||
String.format("No DateFormat defined for property %s. Make sure you have a Converter registered for %s",
|
||||
property, actualType.getSimpleName()));
|
||||
return;
|
||||
}
|
||||
|
||||
ElasticsearchDateConverter converter;
|
||||
@ -177,7 +180,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
if (!StringUtils.hasLength(pattern)) {
|
||||
throw new MappingException(
|
||||
String.format("Property %s is annotated with FieldType.%s and a custom format but has no pattern defined",
|
||||
getOwner().getType().getSimpleName() + "." + getName(), field.type().name()));
|
||||
property, field.type().name()));
|
||||
}
|
||||
|
||||
converter = ElasticsearchDateConverter.of(pattern);
|
||||
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* MoreLikeThisQuery
|
||||
@ -176,6 +177,9 @@ public class MoreLikeThisQuery {
|
||||
}
|
||||
|
||||
public void setPageable(Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "pageable must not be null");
|
||||
|
||||
this.pageable = pageable;
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +54,14 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
||||
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations) {
|
||||
super(method, elasticsearchOperations);
|
||||
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
|
||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
|
||||
this.elasticsearchConverter = elasticsearchOperations.getElasticsearchConverter();
|
||||
this.mappingContext = elasticsearchConverter.getMappingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
Class<?> clazz = queryMethod.getEntityInformation().getJavaType();
|
||||
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
|
||||
CriteriaQuery query = createQuery(accessor);
|
||||
@ -126,7 +126,9 @@ public class ElasticsearchPartQuery extends AbstractElasticsearchRepositoryQuery
|
||||
result = elasticsearchOperations.searchOne(query, clazz, index);
|
||||
}
|
||||
|
||||
return queryMethod.isNotSearchHitMethod() ? SearchHitSupport.unwrapSearchHits(result) : result;
|
||||
return (queryMethod.isNotSearchHitMethod() && !queryMethod.isSearchPageMethod())
|
||||
? SearchHitSupport.unwrapSearchHits(result)
|
||||
: result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,22 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHitSupport;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.convert.DateTimeConverters;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* ElasticsearchStringQuery
|
||||
@ -43,25 +38,8 @@ import org.springframework.util.NumberUtils;
|
||||
*/
|
||||
public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery {
|
||||
|
||||
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
|
||||
private String query;
|
||||
|
||||
private final GenericConversionService conversionService = new GenericConversionService();
|
||||
|
||||
{
|
||||
if (!conversionService.canConvert(java.util.Date.class, String.class)) {
|
||||
conversionService.addConverter(DateTimeConverters.JavaDateConverter.INSTANCE);
|
||||
}
|
||||
if (ClassUtils.isPresent("org.joda.time.DateTimeZone", ElasticsearchStringQuery.class.getClassLoader())) {
|
||||
if (!conversionService.canConvert(org.joda.time.ReadableInstant.class, String.class)) {
|
||||
conversionService.addConverter(DateTimeConverters.JodaDateTimeConverter.INSTANCE);
|
||||
}
|
||||
if (!conversionService.canConvert(org.joda.time.LocalDateTime.class, String.class)) {
|
||||
conversionService.addConverter(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||
String query) {
|
||||
super(queryMethod, elasticsearchOperations);
|
||||
@ -71,7 +49,7 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
Class<?> clazz = queryMethod.getEntityInformation().getJavaType();
|
||||
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
|
||||
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
|
||||
|
||||
StringQuery stringQuery = createQuery(accessor);
|
||||
@ -89,52 +67,32 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue
|
||||
if (queryMethod.isPageQuery()) {
|
||||
stringQuery.setPageable(accessor.getPageable());
|
||||
SearchHits<?> searchHits = elasticsearchOperations.search(stringQuery, clazz, index);
|
||||
result = SearchHitSupport.page(searchHits, stringQuery.getPageable());
|
||||
} else if (queryMethod.isStreamQuery()) {
|
||||
if (accessor.getPageable().isUnpaged()) {
|
||||
stringQuery.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||
if (queryMethod.isSearchPageMethod()) {
|
||||
result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable());
|
||||
} else {
|
||||
stringQuery.setPageable(accessor.getPageable());
|
||||
result = SearchHitSupport
|
||||
.unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()));
|
||||
}
|
||||
} else if (queryMethod.isStreamQuery()) {
|
||||
stringQuery.setPageable(
|
||||
accessor.getPageable().isPaged() ? accessor.getPageable() : PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE));
|
||||
result = StreamUtils.createStreamFromIterator(elasticsearchOperations.searchForStream(stringQuery, clazz, index));
|
||||
} else if (queryMethod.isCollectionQuery()) {
|
||||
if (accessor.getPageable().isPaged()) {
|
||||
stringQuery.setPageable(accessor.getPageable());
|
||||
}
|
||||
stringQuery.setPageable(accessor.getPageable().isPaged() ? accessor.getPageable() : Pageable.unpaged());
|
||||
result = elasticsearchOperations.search(stringQuery, clazz, index);
|
||||
} else {
|
||||
result = elasticsearchOperations.searchOne(stringQuery, clazz, index);
|
||||
}
|
||||
|
||||
return queryMethod.isNotSearchHitMethod() ? SearchHitSupport.unwrapSearchHits(result) : result;
|
||||
return (queryMethod.isNotSearchHitMethod() && !queryMethod.isSearchPageMethod())
|
||||
? SearchHitSupport.unwrapSearchHits(result)
|
||||
: result;
|
||||
}
|
||||
|
||||
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
|
||||
String queryString = replacePlaceholders(this.query, parameterAccessor);
|
||||
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
|
||||
.replacePlaceholders(this.query, parameterAccessor);
|
||||
return new StringQuery(queryString);
|
||||
}
|
||||
|
||||
private String replacePlaceholders(String input, ParametersParameterAccessor accessor) {
|
||||
|
||||
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
|
||||
String result = input;
|
||||
while (matcher.find()) {
|
||||
|
||||
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
|
||||
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
|
||||
result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getParameterWithIndex(ParametersParameterAccessor accessor, int index) {
|
||||
Object parameter = accessor.getBindableValue(index);
|
||||
if (parameter == null) {
|
||||
return "null";
|
||||
}
|
||||
if (conversionService.canConvert(parameter.getClass(), String.class)) {
|
||||
return conversionService.convert(parameter, String.class);
|
||||
}
|
||||
return parameter.toString();
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@ -32,7 +28,6 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsearchRepositoryQuery {
|
||||
|
||||
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
|
||||
private final String query;
|
||||
|
||||
public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
@ -52,27 +47,12 @@ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsea
|
||||
|
||||
@Override
|
||||
protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) {
|
||||
String queryString = replacePlaceholders(this.query, parameterAccessor);
|
||||
String queryString = new StringQueryUtil(
|
||||
getElasticsearchOperations().getElasticsearchConverter().getConversionService()).replacePlaceholders(this.query,
|
||||
parameterAccessor);
|
||||
return new StringQuery(queryString);
|
||||
}
|
||||
|
||||
private String replacePlaceholders(String input, ElasticsearchParameterAccessor accessor) {
|
||||
|
||||
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
|
||||
String result = input;
|
||||
while (matcher.find()) {
|
||||
|
||||
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
|
||||
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
|
||||
result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getParameterWithIndex(ElasticsearchParameterAccessor accessor, int index) {
|
||||
return ObjectUtils.nullSafeToString(accessor.getBindableValue(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isCountQuery() {
|
||||
return false;
|
||||
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.repository.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.elasticsearch.core.convert.DateTimeConverters;
|
||||
import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Niklas Herder
|
||||
*/
|
||||
final public class StringQueryUtil {
|
||||
|
||||
private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)");
|
||||
|
||||
private final ConversionService conversionService;
|
||||
private final GenericConversionService genericConversionService;
|
||||
|
||||
public StringQueryUtil(ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(conversionService, "conversionService must not be null");
|
||||
|
||||
this.conversionService = conversionService;
|
||||
genericConversionService = setupGenericConversionService();
|
||||
}
|
||||
|
||||
private GenericConversionService setupGenericConversionService() {
|
||||
|
||||
GenericConversionService genericConversionService = new GenericConversionService();
|
||||
|
||||
if (!genericConversionService.canConvert(java.util.Date.class, String.class)) {
|
||||
genericConversionService.addConverter(DateTimeConverters.JavaDateConverter.INSTANCE);
|
||||
}
|
||||
|
||||
if (ClassUtils.isPresent("org.joda.time.DateTimeZone", ElasticsearchStringQuery.class.getClassLoader())) {
|
||||
if (!genericConversionService.canConvert(org.joda.time.ReadableInstant.class, String.class)) {
|
||||
genericConversionService.addConverter(DateTimeConverters.JodaDateTimeConverter.INSTANCE);
|
||||
}
|
||||
if (!genericConversionService.canConvert(org.joda.time.LocalDateTime.class, String.class)) {
|
||||
genericConversionService.addConverter(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE);
|
||||
}
|
||||
}
|
||||
return genericConversionService;
|
||||
}
|
||||
|
||||
public String replacePlaceholders(String input, ParameterAccessor accessor) {
|
||||
|
||||
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
|
||||
String result = input;
|
||||
while (matcher.find()) {
|
||||
|
||||
String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)";
|
||||
int index = NumberUtils.parseNumber(matcher.group(1), Integer.class);
|
||||
result = result.replaceAll(placeholder, Matcher.quoteReplacement(getParameterWithIndex(accessor, index)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getParameterWithIndex(ParameterAccessor accessor, int index) {
|
||||
|
||||
Object parameter = accessor.getBindableValue(index);
|
||||
String parameterValue = "null";
|
||||
|
||||
// noinspection ConstantConditions
|
||||
if (parameter != null) {
|
||||
|
||||
parameterValue = convert(parameter);
|
||||
}
|
||||
|
||||
return parameterValue;
|
||||
|
||||
}
|
||||
|
||||
private String convert(Object parameter) {
|
||||
if (Collection.class.isAssignableFrom(parameter.getClass())) {
|
||||
Collection<?> collectionParam = (Collection<?>) parameter;
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
sb.append(collectionParam.stream().map(o -> {
|
||||
if (o instanceof String) {
|
||||
return "\"" + convert(o) + "\"";
|
||||
} else {
|
||||
return convert(o);
|
||||
}
|
||||
}).collect(Collectors.joining(",")));
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
} else {
|
||||
String parameterValue = "null";
|
||||
if (conversionService.canConvert(parameter.getClass(), String.class)) {
|
||||
String converted = conversionService.convert(parameter, String.class);
|
||||
|
||||
if (converted != null) {
|
||||
parameterValue = converted;
|
||||
}
|
||||
} else if (genericConversionService.canConvert(parameter.getClass(), String.class)) {
|
||||
String converted = genericConversionService.convert(parameter, String.class);
|
||||
|
||||
if (converted != null) {
|
||||
parameterValue = converted;
|
||||
|
||||
}
|
||||
} else {
|
||||
parameterValue = parameter.toString();
|
||||
}
|
||||
|
||||
parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\""));
|
||||
return parameterValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,251 @@
|
||||
Spring Data Elasticsearch Changelog
|
||||
===================================
|
||||
|
||||
Changes in version 4.1.11 (2021-07-16)
|
||||
--------------------------------------
|
||||
* #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion.
|
||||
* #1858 - Collection parameters for @Query-annotated methods get escaped wrongly.
|
||||
* #1846 - Missing hashCode and equals methods in JoinField.
|
||||
|
||||
|
||||
Changes in version 4.2.2 (2021-06-22)
|
||||
-------------------------------------
|
||||
* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics].
|
||||
|
||||
|
||||
Changes in version 4.1.10 (2021-06-22)
|
||||
--------------------------------------
|
||||
* #1843 - Pageable results and @Query annotation.
|
||||
* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics].
|
||||
|
||||
|
||||
Changes in version 4.2.1 (2021-05-14)
|
||||
-------------------------------------
|
||||
* #1811 - StringQuery execution crashes on return type `SearchPage<T>`.
|
||||
* #1805 - Upgrade to Elasticsearch 7.12.1.
|
||||
* #1794 - Refactor `DefaultReactiveElasticsearchClient` to do request customization with the `WebClient`.
|
||||
* #1790 - Custom Query with string parameter which contains double quotes.
|
||||
* #1787 - Search with MoreLikeThisQuery should use Pageable.
|
||||
* #1785 - Fix documentation about auditing.
|
||||
* #1778 - Custom property names must be used in SourceFilter and source fields.
|
||||
* #1767 - DynamicMapping annotation should be applicable to any object field.
|
||||
|
||||
|
||||
Changes in version 4.1.9 (2021-05-14)
|
||||
-------------------------------------
|
||||
* #1811 - StringQuery execution crashes on return type `SearchPage<T>`.
|
||||
* #1790 - Custom Query with string parameter which contains double quotes.
|
||||
* #1787 - Search with MoreLikeThisQuery should use Pageable.
|
||||
* #1785 - Fix documentation about auditing.
|
||||
* #1767 - DynamicMapping annotation should be applicable to any object field.
|
||||
|
||||
|
||||
Changes in version 4.2.0 (2021-04-14)
|
||||
-------------------------------------
|
||||
* #1771 - Remove `@Persistent` from entity-scan include filters.
|
||||
* #1761 - CriteriaQuery must use nested query only with properties of type nested.
|
||||
* #1759 - health check with DefaultReactiveElasticsearchClient.
|
||||
* #1758 - Nested Criteria queries must consider sub-fields.
|
||||
* #1755 - Documentation fix to not show deprecated calls.
|
||||
* #1754 - Types are in the process of being removed.
|
||||
* #1753 - CriteriaQuery must support nested queries.
|
||||
* #1390 - Introduce ClusterOperations [DATAES-818].
|
||||
|
||||
|
||||
Changes in version 4.1.8 (2021-04-14)
|
||||
-------------------------------------
|
||||
* #1759 - health check with DefaultReactiveElasticsearchClient.
|
||||
|
||||
|
||||
Changes in version 4.0.9.RELEASE (2021-04-14)
|
||||
---------------------------------------------
|
||||
* #1759 - health check with DefaultReactiveElasticsearchClient.
|
||||
|
||||
|
||||
Changes in version 4.1.7 (2021-03-31)
|
||||
-------------------------------------
|
||||
|
||||
|
||||
Changes in version 4.2.0-RC1 (2021-03-31)
|
||||
-----------------------------------------
|
||||
* #1745 - Automatically close scroll context when returning streamed results.
|
||||
* #1741 - Upgrade to Elasticsearch 7.12.
|
||||
* #1738 - Readme lists artifacts with .RELEASE and .BUILD-SNAPSHOT suffixes.
|
||||
* #1736 - Upgrade to OpenWebBeans 2.0.
|
||||
* #1734 - Remove lombok.
|
||||
* #1733 - Update CI to Java 16.
|
||||
* #1727 - Allow multiple date formats for date fields.
|
||||
* #1719 - Configure index settings with @Setting annotation.
|
||||
|
||||
|
||||
Changes in version 4.2.0-M5 (2021-03-17)
|
||||
----------------------------------------
|
||||
* #1725 - Add support for SearchTemplate for reactive client.
|
||||
* #1721 - IndexOps.getMapping raises exception if mapping contains "dynamic_templates".
|
||||
* #1718 - Create index with mapping in one step.
|
||||
* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch.
|
||||
* #1711 - Add the type hint _class attribute to the index mapping.
|
||||
* #1704 - Add SearchFailure field in ByQueryResponse.
|
||||
* #1700 - Add missing "Document ranking types".
|
||||
* #1687 - Upgrade to Elasticsearch 7.11.
|
||||
* #1686 - Add rescore functionality.
|
||||
* #1678 - Errors are silent in multiGet.
|
||||
* #1658 - ReactiveElasticsearchClient should use the same request parameters as non-reactive code.
|
||||
* #1646 - Add function to list all indexes.
|
||||
* #1514 - Add `matched_queries` field in SearchHit [DATAES-979].
|
||||
|
||||
|
||||
Changes in version 4.1.6 (2021-03-17)
|
||||
-------------------------------------
|
||||
* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch.
|
||||
|
||||
|
||||
Changes in version 4.0.8.RELEASE (2021-03-17)
|
||||
---------------------------------------------
|
||||
* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch.
|
||||
|
||||
|
||||
Changes in version 4.2.0-M4 (2021-02-18)
|
||||
----------------------------------------
|
||||
|
||||
|
||||
Changes in version 4.1.5 (2021-02-18)
|
||||
-------------------------------------
|
||||
|
||||
|
||||
Changes in version 4.2.0-M3 (2021-02-17)
|
||||
----------------------------------------
|
||||
* #1689 - Missing anchor links in documentation.
|
||||
* #1680 - After upgrade to 4.x can't read property id from _source named (different value from _id).
|
||||
* #1679 - Errors are silent in delete by query in ReactiveElasticsearchTemplate.
|
||||
* #1676 - Align MappingElasticsearchConverter with other Spring Data converters.
|
||||
* #1675 - Consider Document as simple type.
|
||||
* #1669 - Cleanup Deprecations from 4.0.
|
||||
* #1668 - Writing a more complex CriteriaQuery.
|
||||
* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address.
|
||||
* #1665 - ReactiveElasticsearchOperations indexName twice endcoding.
|
||||
* #1662 - Documentation fix.
|
||||
* #1659 - Fix source filter setup in multiget requests.
|
||||
* #1655 - GeoJson types can be lowercase in Elasticsearch.
|
||||
* #1649 - Upgrade to Elasticsearch 7.10.2.
|
||||
* #1647 - Use own implementation of date formatters.
|
||||
* #1644 - Implement update by query.
|
||||
* #1565 - Allow using FieldNamingStrategy for property to fieldname matching [DATAES-993].
|
||||
* #1370 - Add enabled mapping parameter to FieldType configuration [DATAES-798].
|
||||
* #1218 - Add routing parameter to ElasticsearchOperations [DATAES-644].
|
||||
* #1156 - Add @CountQuery annotation [DATAES-584].
|
||||
* #1143 - Support for search_after [DATAES-571].
|
||||
* #803 - Don't update indexed object if it is no persistent entity [DATAES-229].
|
||||
* #725 - Add query Explain Support [DATAES-149].
|
||||
|
||||
|
||||
Changes in version 4.1.4 (2021-02-17)
|
||||
-------------------------------------
|
||||
* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address.
|
||||
* #1665 - ReactiveElasticsearchOperations indexName twice endcoding.
|
||||
* #1662 - Documentation fix.
|
||||
* #1659 - Fix source filter setup in multiget requests.
|
||||
* #1655 - GeoJson types can be lowercase in Elasticsearch.
|
||||
|
||||
|
||||
Changes in version 4.0.7.RELEASE (2021-02-17)
|
||||
---------------------------------------------
|
||||
* DATAES-996 - Update CI jobs with Docker Login.
|
||||
* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address.
|
||||
* #1665 - ReactiveElasticsearchOperations indexName twice endcoding.
|
||||
* #1662 - Documentation fix.
|
||||
* #1659 - Fix source filter setup in multiget requests.
|
||||
|
||||
|
||||
Changes in version 3.2.13.RELEASE (2021-02-17)
|
||||
----------------------------------------------
|
||||
* #1694 - Upgrade to Elasticsearch 6.8.14.
|
||||
* #1662 - Documentation fix.
|
||||
|
||||
|
||||
Changes in version 4.2.0-M2 (2021-01-13)
|
||||
----------------------------------------
|
||||
* DATAES-1003 - add timeout to search query.
|
||||
* DATAES-996 - Update CI jobs with Docker Login.
|
||||
* DATAES-982 - Improve refresh handling.
|
||||
* DATAES-946 - Support 'wildcard' field type.
|
||||
* #1640 - Add support for GetFieldMapping request in ReactiveElasticsearchClient.
|
||||
* #1638 - Upgrade to Elasticsearch 7.10.1.
|
||||
* #1634 - Update Testcontainers dependency.
|
||||
* #1632 - Update copyright notice to 2021.
|
||||
* #1629 - Update repository after GitHub issues migration.
|
||||
* #1576 - Add version of Spring dependency to docs [DATAES-1004].
|
||||
* #1056 - Repository initialization should throw an Exception when index cannot be created [DATAES-481].
|
||||
|
||||
|
||||
Changes in version 4.1.3 (2021-01-13)
|
||||
-------------------------------------
|
||||
* DATAES-996 - Update CI jobs with Docker Login.
|
||||
* #1634 - Update Testcontainers dependency.
|
||||
|
||||
|
||||
Changes in version 4.1.2 (2020-12-09)
|
||||
-------------------------------------
|
||||
* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads).
|
||||
* DATAES-990 - Index creation fails with Authentication object cannot be null on startup.
|
||||
* DATAES-987 - IndexOperations getMapping fail when using index alias.
|
||||
* DATAES-978 - Accept DateFormat.none for a date property to enable custom Converters.
|
||||
* DATAES-977 - Fix versions in reference documentation for 4.1.
|
||||
* DATAES-973 - Release 4.1.2 (2020.0.2).
|
||||
* DATAES-972 - BeforeConvertCallback should be called before index query is built.
|
||||
* DATAES-543 - Adjust configuration support classes so they do not require proxying.
|
||||
|
||||
|
||||
Changes in version 4.2.0-M1 (2020-12-09)
|
||||
----------------------------------------
|
||||
* DATAES-995 - Code Cleanup after DATACMNS-1838.
|
||||
* DATAES-994 - Add setup for mutation testing.
|
||||
* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads).
|
||||
* DATAES-990 - Index creation fails with Authentication object cannot be null on startup.
|
||||
* DATAES-989 - Improve deprecation warning for id properties without annotation.
|
||||
* DATAES-988 - Allow specifying max results in NativeSearchQueryBuilder.
|
||||
* DATAES-987 - IndexOperations getMapping fail when using index alias.
|
||||
* DATAES-986 - Fix Javadoc.
|
||||
* DATAES-985 - Add builder method for track_total_hits to NativeSearchQueryBuilder.
|
||||
* DATAES-983 - Test dependency hoverfly-java-junit5 leaks into compile scope.
|
||||
* DATAES-978 - Accept DateFormat.none for a date property to enable custom Converters.
|
||||
* DATAES-976 - Implement CrudRepository.delete(Iterable<ID> ids).
|
||||
* DATAES-975 - Upgrade to Elasticsearch 7.10.
|
||||
* DATAES-974 - remove usage of deprecated WebClient exchange() method.
|
||||
* DATAES-972 - BeforeConvertCallback should be called before index query is built.
|
||||
* DATAES-971 - Fix tests for using a proxy with reactive client.
|
||||
* DATAES-970 - Take Testcontainers version from the Spring Data Build pom.
|
||||
* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree.
|
||||
* DATAES-968 - Enable Maven caching for Jenkins jobs.
|
||||
* DATAES-966 - Release 4.2 M1 (2021.0.0).
|
||||
* DATAES-882 - HLRC Configuration - add ability to set max connections for the underlying HttpClient.
|
||||
* DATAES-588 - Add support for custom callbacks in High Level/Low Level REST Client builder.
|
||||
* DATAES-543 - Adjust configuration support classes so they do not require proxying.
|
||||
* DATAES-362 - Add support for composable meta annotations.
|
||||
* DATAES-247 - Support OpType in IndexQuery.
|
||||
|
||||
|
||||
Changes in version 4.0.6.RELEASE (2020-12-09)
|
||||
---------------------------------------------
|
||||
* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads).
|
||||
* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree.
|
||||
* DATAES-968 - Enable Maven caching for Jenkins jobs.
|
||||
* DATAES-964 - Release 4.0.6 (Neumann SR6).
|
||||
|
||||
|
||||
Changes in version 3.2.12.RELEASE (2020-12-09)
|
||||
----------------------------------------------
|
||||
* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree.
|
||||
* DATAES-963 - Release 3.2.12 (Moore SR12).
|
||||
|
||||
|
||||
Changes in version 4.1.1 (2020-11-11)
|
||||
-------------------------------------
|
||||
* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree.
|
||||
* DATAES-968 - Enable Maven caching for Jenkins jobs.
|
||||
* DATAES-965 - Release 4.1.1 (2020.0.1).
|
||||
|
||||
|
||||
Changes in version 4.1.0 (2020-10-28)
|
||||
-------------------------------------
|
||||
* DATAES-962 - Deprecate Joda support.
|
||||
@ -1363,6 +1608,32 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
Spring Data Elasticsearch 4.1 GA (2020.0.0)
|
||||
Spring Data Elasticsearch 4.1.15 (2020.0.15)
|
||||
Copyright (c) [2013-2019] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@ -15,6 +15,21 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -22,20 +22,29 @@ import static org.mockito.Mockito.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
import org.elasticsearch.action.get.GetRequest;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
@ -46,29 +55,23 @@ class DefaultReactiveElasticsearchClientTest {
|
||||
@Mock private HostProvider hostProvider;
|
||||
|
||||
@Mock private Function<SearchRequest, Request> searchRequestConverter;
|
||||
@Spy private RequestCreator requestCreator;
|
||||
|
||||
private DefaultReactiveElasticsearchClient client;
|
||||
@Mock private WebClient webClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
client = new DefaultReactiveElasticsearchClient(hostProvider, new RequestCreator() {
|
||||
@Override
|
||||
public Function<SearchRequest, Request> search() {
|
||||
return searchRequestConverter;
|
||||
}
|
||||
}) {
|
||||
@Test
|
||||
void shouldSetAppropriateRequestParametersOnCount() {
|
||||
|
||||
when(requestCreator.search()).thenReturn(searchRequestConverter);
|
||||
SearchRequest searchRequest = new SearchRequest("someindex") //
|
||||
.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));
|
||||
|
||||
ReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator) {
|
||||
@Override
|
||||
public Mono<ResponseSpec> execute(ReactiveElasticsearchClientCallback callback) {
|
||||
return Mono.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetAppropriateRequestParametersOnCount() {
|
||||
|
||||
SearchRequest searchRequest = new SearchRequest("someindex") //
|
||||
.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));
|
||||
|
||||
client.count(searchRequest).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
@ -79,4 +82,33 @@ class DefaultReactiveElasticsearchClientTest {
|
||||
assertThat(source.trackTotalHitsUpTo()).isEqualTo(TRACK_TOTAL_HITS_ACCURATE);
|
||||
assertThat(source.fetchSource()).isEqualTo(FetchSourceContext.DO_NOT_FETCH_SOURCE);
|
||||
}
|
||||
|
||||
@Test // #1712
|
||||
@DisplayName("should throw ElasticsearchStatusException on server 5xx with empty body")
|
||||
void shouldThrowElasticsearchStatusExceptionOnServer5xxWithEmptyBody() {
|
||||
|
||||
when(hostProvider.getActive(any())).thenReturn(Mono.just(webClient));
|
||||
WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
|
||||
when(requestBodyUriSpec.uri((Function<UriBuilder, URI>) any())).thenReturn(requestBodyUriSpec);
|
||||
when(requestBodyUriSpec.attribute(any(), any())).thenReturn(requestBodyUriSpec);
|
||||
when(requestBodyUriSpec.headers(any())).thenReturn(requestBodyUriSpec);
|
||||
when(webClient.method(any())).thenReturn(requestBodyUriSpec);
|
||||
when(requestBodyUriSpec.exchangeToMono(any())).thenAnswer(invocationOnMock -> {
|
||||
Function<ClientResponse, ? extends Mono<?>> responseHandler = invocationOnMock.getArgument(0);
|
||||
ClientResponse clientResponse = mock(ClientResponse.class);
|
||||
when(clientResponse.statusCode()).thenReturn(HttpStatus.SERVICE_UNAVAILABLE);
|
||||
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
|
||||
when(headers.contentType()).thenReturn(Optional.empty());
|
||||
when(clientResponse.headers()).thenReturn(headers);
|
||||
when(clientResponse.body(any())).thenReturn(Mono.empty());
|
||||
return responseHandler.apply(clientResponse);
|
||||
});
|
||||
|
||||
ReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator);
|
||||
|
||||
client.get(new GetRequest("42")) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectError(ElasticsearchStatusException.class) //
|
||||
.verify(); //
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -62,7 +62,7 @@ public class ReactiveElasticsearchClientUnitTests {
|
||||
|
||||
static final String HOST = ":9200";
|
||||
|
||||
MockDelegatingElasticsearchHostProvider<HostProvider> hostProvider;
|
||||
MockDelegatingElasticsearchHostProvider<? extends HostProvider<?>> hostProvider;
|
||||
ReactiveElasticsearchClient client;
|
||||
|
||||
@BeforeEach
|
||||
|
@ -186,7 +186,7 @@ public class ReactiveMockClientTestsUtils {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public MockDelegatingElasticsearchHostProvider<T> withActiveDefaultHost(String host) {
|
||||
public MockDelegatingElasticsearchHostProvider<? extends HostProvider<?>> withActiveDefaultHost(String host) {
|
||||
return new MockDelegatingElasticsearchHostProvider(HttpHeaders.EMPTY, clientProvider, errorCollector, delegate,
|
||||
host);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2020 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.
|
||||
@ -30,6 +30,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveMockClient
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class SingleNodeHostProviderUnitTests {
|
||||
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.config;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -114,6 +115,7 @@ public class ElasticsearchConfigurationSupportUnitTests {
|
||||
static class ReactiveRestConfig extends AbstractReactiveElasticsearchConfiguration {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
|
||||
ReactiveElasticsearchClient client = mock(ReactiveElasticsearchClient.class);
|
||||
when(client.info()).thenReturn(Mono
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 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.
|
||||
@ -121,14 +121,12 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class ElasticsearchTemplateTests {
|
||||
|
||||
protected static final String INDEX_NAME_JOIN_SAMPLE_ENTITY = "test-index-sample-join-template";
|
||||
private static final String INDEX_NAME_SAMPLE_ENTITY = "test-index-sample-core-template";
|
||||
private static final String INDEX_1_NAME = "test-index-1";
|
||||
private static final String INDEX_2_NAME = "test-index-2";
|
||||
private static final String INDEX_3_NAME = "test-index-3";
|
||||
|
||||
protected final IndexCoordinates index = IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY);
|
||||
protected static final String INDEX_NAME_JOIN_SAMPLE_ENTITY = "test-index-sample-join-template";
|
||||
|
||||
@Autowired protected ElasticsearchOperations operations;
|
||||
protected IndexOperations indexOperations;
|
||||
|
||||
@ -1097,6 +1095,51 @@ public abstract class ElasticsearchTemplateTests {
|
||||
assertThat(content).contains(sampleEntity);
|
||||
}
|
||||
|
||||
@Test // #1787
|
||||
@DisplayName("should use Pageable on MoreLikeThis queries")
|
||||
void shouldUsePageableOnMoreLikeThisQueries() {
|
||||
|
||||
String sampleMessage = "So we build a web site or an application and want to add search to it, "
|
||||
+ "and then it hits us: getting search working is hard. We want our search solution to be fast,"
|
||||
+ " we want a painless setup and a completely free search schema, we want to be able to index data simply using JSON over HTTP, "
|
||||
+ "we want our search server to be always available, we want to be able to start with one machine and scale to hundreds, "
|
||||
+ "we want real-time search, we want simple multi-tenancy, and we want a solution that is built for the cloud.";
|
||||
String referenceId = nextIdAsString();
|
||||
Collection<String> ids = IntStream.rangeClosed(1, 10).mapToObj(i -> nextIdAsString()).collect(Collectors.toList());
|
||||
ids.add(referenceId);
|
||||
ids.stream()
|
||||
.map(id -> getIndexQuery(
|
||||
SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build()))
|
||||
.forEach(indexQuery -> operations.index(indexQuery, index));
|
||||
indexOperations.refresh();
|
||||
|
||||
MoreLikeThisQuery moreLikeThisQuery = new MoreLikeThisQuery();
|
||||
moreLikeThisQuery.setId(referenceId);
|
||||
moreLikeThisQuery.addFields("message");
|
||||
moreLikeThisQuery.setMinDocFreq(1);
|
||||
moreLikeThisQuery.setPageable(PageRequest.of(0, 5));
|
||||
|
||||
SearchHits<SampleEntity> searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(10);
|
||||
assertThat(searchHits.getSearchHits()).hasSize(5);
|
||||
|
||||
Collection<String> returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
moreLikeThisQuery.setPageable(PageRequest.of(1, 5));
|
||||
|
||||
searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(10);
|
||||
assertThat(searchHits.getSearchHits()).hasSize(5);
|
||||
|
||||
searchHits.getSearchHits().stream().map(SearchHit::getId).forEach(returnedIds::add);
|
||||
|
||||
assertThat(returnedIds).hasSize(10);
|
||||
assertThat(ids).containsAll(returnedIds);
|
||||
}
|
||||
|
||||
@Test // DATAES-167
|
||||
public void shouldReturnResultsWithScanAndScrollForGivenCriteriaQuery() {
|
||||
|
||||
@ -1466,6 +1509,30 @@ public abstract class ElasticsearchTemplateTests {
|
||||
assertThat(mapping.get("properties")).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAES-987
|
||||
@DisplayName("should read mappings from alias")
|
||||
void shouldReadMappingsFromAlias() {
|
||||
|
||||
String aliasName = INDEX_NAME_SAMPLE_ENTITY + "alias";
|
||||
indexOperations.alias( //
|
||||
new AliasActions( //
|
||||
new AliasAction.Add( //
|
||||
AliasActionParameters.builder() //
|
||||
.withIndices(INDEX_NAME_SAMPLE_ENTITY) //
|
||||
.withAliases(aliasName) //
|
||||
.build()) //
|
||||
) //
|
||||
);
|
||||
|
||||
IndexOperations aliasIndexOps = operations.indexOps(IndexCoordinates.of(aliasName));
|
||||
Map<String, Object> mappingFromAlias = aliasIndexOps.getMapping();
|
||||
|
||||
assertThat(mappingFromAlias).isNotNull();
|
||||
assertThat(
|
||||
((Map<String, Object>) ((Map<String, Object>) mappingFromAlias.get("properties")).get("message")).get("type"))
|
||||
.isEqualTo("text");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDeleteIndexForGivenEntity() {
|
||||
|
||||
@ -3604,6 +3671,21 @@ public abstract class ElasticsearchTemplateTests {
|
||||
softly.assertThat(searchHits.getTotalHitsRelation()).isEqualTo(TotalHitsRelation.OFF);
|
||||
softly.assertAll();
|
||||
}
|
||||
@Test // #1893
|
||||
@DisplayName("should index document from source with version")
|
||||
void shouldIndexDocumentFromSourceWithVersion() {
|
||||
|
||||
String source = "{\n" + //
|
||||
" \"answer\": 42\n" + //
|
||||
"}";
|
||||
IndexQuery query = new IndexQueryBuilder() //
|
||||
.withId("42") //
|
||||
.withSource(source) //
|
||||
.withVersion(42L) //
|
||||
.build();
|
||||
|
||||
operations.index(query, IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY));
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ -3800,4 +3882,5 @@ public abstract class ElasticsearchTemplateTests {
|
||||
@JoinTypeRelation(parent = "question", children = { "answer" }) }) private JoinField<String> myJoinField;
|
||||
@Field(type = Text) private String text;
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ import java.lang.Boolean;
|
||||
import java.lang.Long;
|
||||
import java.lang.Object;
|
||||
import java.net.ConnectException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -1037,6 +1039,32 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
|
||||
assertThat(searchHits.getSearchHits().size()).isEqualTo(5);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1665
|
||||
@DisplayName("should be able to process date-math-index names")
|
||||
void shouldBeAbleToProcessDateMathIndexNames() {
|
||||
|
||||
String indexName = "foo-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM"));
|
||||
String dateMathIndexName = "<foo-{now/M{yyyy.MM}}>";
|
||||
|
||||
template.indexOps(IndexCoordinates.of(dateMathIndexName)) //
|
||||
.create() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete(); //
|
||||
|
||||
template.indexOps(IndexCoordinates.of(indexName)) //
|
||||
.exists() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete(); //
|
||||
|
||||
template.indexOps(IndexCoordinates.of(dateMathIndexName)) //
|
||||
.delete() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete(); //
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Helper functions
|
||||
@ -1134,5 +1162,6 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
|
||||
@Id private String id;
|
||||
@Version private Long version;
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.core.query.SourceFilter;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
|
||||
public class SourceFilterIntegrationTests {
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
private IndexOperations indexOps;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexOps = operations.indexOps(Entity.class);
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
|
||||
operations.save(Entity.builder().id("42").field1("one").field2("two").field3("three").build());
|
||||
indexOps.refresh();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
indexOps.delete();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should only return requested fields on search")
|
||||
void shouldOnlyReturnRequestedFieldsOnSearch() {
|
||||
|
||||
Query query = Query.findAll();
|
||||
query.addFields("field2");
|
||||
|
||||
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
|
||||
|
||||
assertThat(searchHits).hasSize(1);
|
||||
Entity entity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(entity.getField1()).isNull();
|
||||
assertThat(entity.getField2()).isEqualTo("two");
|
||||
assertThat(entity.getField3()).isNull();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should only return requested fields on multiget")
|
||||
void shouldOnlyReturnRequestedFieldsOnGMultiGet() {
|
||||
|
||||
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
|
||||
query.addFields("field2");
|
||||
|
||||
List<Entity> entities = operations.multiGet(query, Entity.class);
|
||||
|
||||
assertThat(entities).hasSize(1);
|
||||
Entity entity = entities.get(0);
|
||||
assertThat(entity.getField1()).isNull();
|
||||
assertThat(entity.getField2()).isEqualTo("two");
|
||||
assertThat(entity.getField3()).isNull();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should not return excluded fields from SourceFilter on search")
|
||||
void shouldNotReturnExcludedFieldsFromSourceFilterOnSearch() {
|
||||
|
||||
Query query = Query.findAll();
|
||||
query.addSourceFilter(new SourceFilter() {
|
||||
@Override
|
||||
public String[] getIncludes() {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExcludes() {
|
||||
return new String[] { "field2" };
|
||||
}
|
||||
});
|
||||
|
||||
SearchHits<Entity> entities = operations.search(query, Entity.class);
|
||||
|
||||
assertThat(entities).hasSize(1);
|
||||
Entity entity = entities.getSearchHit(0).getContent();
|
||||
assertThat(entity.getField1()).isNotNull();
|
||||
assertThat(entity.getField2()).isNull();
|
||||
assertThat(entity.getField3()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should not return excluded fields from SourceFilter on multiget")
|
||||
void shouldNotReturnExcludedFieldsFromSourceFilterOnMultiGet() {
|
||||
|
||||
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
|
||||
query.addSourceFilter(new SourceFilter() {
|
||||
@Override
|
||||
public String[] getIncludes() {
|
||||
return new String[] {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExcludes() {
|
||||
return new String[] { "field2" };
|
||||
}
|
||||
});
|
||||
|
||||
List<Entity> entities = operations.multiGet(query, Entity.class);
|
||||
|
||||
assertThat(entities).hasSize(1);
|
||||
Entity entity = entities.get(0);
|
||||
assertThat(entity.getField1()).isNotNull();
|
||||
assertThat(entity.getField2()).isNull();
|
||||
assertThat(entity.getField3()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should only return included fields from SourceFilter on search")
|
||||
void shouldOnlyReturnIncludedFieldsFromSourceFilterOnSearch() {
|
||||
|
||||
Query query = Query.findAll();
|
||||
query.addSourceFilter(new SourceFilter() {
|
||||
@Override
|
||||
public String[] getIncludes() {
|
||||
return new String[] { "field2" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExcludes() {
|
||||
return new String[] {};
|
||||
}
|
||||
});
|
||||
|
||||
SearchHits<Entity> entities = operations.search(query, Entity.class);
|
||||
|
||||
assertThat(entities).hasSize(1);
|
||||
Entity entity = entities.getSearchHit(0).getContent();
|
||||
assertThat(entity.getField1()).isNull();
|
||||
assertThat(entity.getField2()).isNotNull();
|
||||
assertThat(entity.getField3()).isNull();
|
||||
}
|
||||
|
||||
@Test // #1659
|
||||
@DisplayName("should only return included fields from SourceFilter on multiget")
|
||||
void shouldOnlyReturnIncludedFieldsFromSourceFilterOnMultiGet() {
|
||||
|
||||
Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build();
|
||||
query.addSourceFilter(new SourceFilter() {
|
||||
@Override
|
||||
public String[] getIncludes() {
|
||||
return new String[] { "field2" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getExcludes() {
|
||||
return new String[] {};
|
||||
}
|
||||
});
|
||||
|
||||
List<Entity> entities = operations.multiGet(query, Entity.class);
|
||||
|
||||
assertThat(entities).hasSize(1);
|
||||
Entity entity = entities.get(0);
|
||||
assertThat(entity.getField1()).isNull();
|
||||
assertThat(entity.getField2()).isNotNull();
|
||||
assertThat(entity.getField3()).isNull();
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "sourcefilter-tests")
|
||||
public static class Entity {
|
||||
@Id private String id;
|
||||
@Field(type = FieldType.Text) private String field1;
|
||||
@Field(type = FieldType.Text) private String field2;
|
||||
@Field(type = FieldType.Text) private String field3;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class })
|
||||
public class SourceFilterIntegrationTransportTests extends SourceFilterIntegrationTests {}
|
@ -53,6 +53,9 @@ class GeoConvertersUnitTests {
|
||||
@DisplayName("GeoJsonPoint")
|
||||
class GeoJsonPointUnitTests {
|
||||
|
||||
// NOTE: the test converting from a map contains the type names in lowercase, that might be returned from
|
||||
// Elasticsearch
|
||||
|
||||
@Test // DATAES-930
|
||||
@DisplayName("should be converted to a Map")
|
||||
void shouldBeConvertedToAMap() throws JSONException {
|
||||
@ -75,7 +78,7 @@ class GeoConvertersUnitTests {
|
||||
|
||||
// make sure we can read int values as well
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"Point\",\n" + //
|
||||
" \"type\": \"point\",\n" + //
|
||||
" \"coordinates\": [12, 34.0]\n" + //
|
||||
"}"; //
|
||||
|
||||
@ -117,8 +120,14 @@ class GeoConvertersUnitTests {
|
||||
void shouldBeConvertedFromAMap() {
|
||||
|
||||
// make sure we can read int values as well
|
||||
String json = "{\n" + " \"type\": \"MultiPoint\",\n" + " \"coordinates\": [\n" + " [12.0, 34],\n"
|
||||
+ " [56, 78.0]\n" + " ]\n" + "}\n";
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"multipoint\",\n" //
|
||||
+ " \"coordinates\": [\n" + //
|
||||
" [12.0, 34],\n" + //
|
||||
" [56, 78.0]\n" + //
|
||||
" ]\n" + //
|
||||
"}\n"; //
|
||||
|
||||
Document document = Document.parse(json);
|
||||
|
||||
GeoJsonMultiPoint expected = GeoJsonMultiPoint.of(new Point(12, 34), new Point(56, 78));
|
||||
@ -158,7 +167,7 @@ class GeoConvertersUnitTests {
|
||||
|
||||
// make sure we can read int values as well
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"LineString\",\n" + //
|
||||
" \"type\": \"linestring\",\n" + //
|
||||
" \"coordinates\": [\n" + //
|
||||
" [12.0, 34],\n" + //
|
||||
" [56, 78.0]\n" //
|
||||
@ -205,7 +214,7 @@ class GeoConvertersUnitTests {
|
||||
void shouldBeConvertedFromAMap() {
|
||||
// make sure we can read int values as well
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"MultiLineString\",\n" + //
|
||||
" \"type\": \"multilinestring\",\n" + //
|
||||
" \"coordinates\": [\n" + //
|
||||
" [[12, 34.0], [56.0, 78]],\n" + //
|
||||
" [[90.0, 12], [34, 56.0]]\n" + //
|
||||
@ -256,7 +265,7 @@ class GeoConvertersUnitTests {
|
||||
void shouldBeConvertedFromAMap() {
|
||||
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"Polygon\",\n" + //
|
||||
" \"type\": \"polygon\",\n" + //
|
||||
" \"coordinates\": [\n" + //
|
||||
" [[12, 34.0], [56.0, 78], [90, 12.0], [12, 34.0]],\n" + //
|
||||
" [[56.0, 78], [90, 12.0], [34.0, 56], [56.0, 78]]\n" + //
|
||||
@ -308,7 +317,7 @@ class GeoConvertersUnitTests {
|
||||
void shouldBeConvertedFromAMap() {
|
||||
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"MultiPolygon\",\n" + //
|
||||
" \"type\": \"multipolygon\",\n" + //
|
||||
" \"coordinates\": [\n" + //
|
||||
" [[[12, 34.0], [56.0, 78], [90, 12.0], [12, 34.0]]],\n" + //
|
||||
" [[[56, 78.0], [90, 12.0], [34.0, 56], [56, 78.0]]]\n" + //
|
||||
@ -369,14 +378,14 @@ class GeoConvertersUnitTests {
|
||||
void shouldBeConvertedFromAMap() {
|
||||
|
||||
String json = "{\n" + //
|
||||
" \"type\": \"GeometryCollection\",\n" + //
|
||||
" \"type\": \"geometrycollection\",\n" + //
|
||||
" \"geometries\": [\n" + //
|
||||
" {\n" + //
|
||||
" \"type\": \"Point\",\n" + //
|
||||
" \"type\": \"point\",\n" + //
|
||||
" \"coordinates\": [12.0, 34.0]\n" + //
|
||||
" },\n" + //
|
||||
" {\n" + //
|
||||
" \"type\": \"Polygon\",\n" + //
|
||||
" \"type\": \"polygon\",\n" + //
|
||||
" \"coordinates\": [\n" + //
|
||||
" [[12.0, 34.0], [56.0, 78.0], [90.0, 12.0], [12.0, 34.0]]\n" + //
|
||||
" ]\n" + //
|
||||
|
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2020 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.event;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.JoinTypeRelation;
|
||||
import org.springframework.data.elasticsearch.annotations.JoinTypeRelations;
|
||||
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
abstract class ElasticsearchOperationsCallbackIntegrationTest {
|
||||
|
||||
private static final String INDEX = "test-operations-callback";
|
||||
@Autowired private ElasticsearchOperations originalOperations;
|
||||
// need a spy here on the abstract implementation class
|
||||
private AbstractElasticsearchTemplate operations;
|
||||
|
||||
@Nullable private static SeqNoPrimaryTerm seqNoPrimaryTerm = null;
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Component
|
||||
static class SampleEntityBeforeConvertCallback implements BeforeConvertCallback<SampleEntity> {
|
||||
@Override
|
||||
public SampleEntity onBeforeConvert(SampleEntity entity, IndexCoordinates index) {
|
||||
entity.setText("converted");
|
||||
JoinField<String> joinField = new JoinField<>("answer", "42");
|
||||
entity.setJoinField(joinField);
|
||||
if (seqNoPrimaryTerm != null) {
|
||||
entity.setSeqNoPrimaryTerm(seqNoPrimaryTerm);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
seqNoPrimaryTerm = null;
|
||||
operations = (AbstractElasticsearchTemplate) spy(originalOperations);
|
||||
|
||||
IndexOperations indexOps = operations.indexOps(SampleEntity.class);
|
||||
indexOps.delete();
|
||||
indexOps.create();
|
||||
indexOps.putMapping(SampleEntity.class);
|
||||
|
||||
// store one entity to have a seq_no and primary_term
|
||||
final SampleEntity initial = new SampleEntity("1", "initial");
|
||||
final SampleEntity saved = operations.save(initial);
|
||||
seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
IndexOperations indexOps = operations.indexOps(SampleEntity.class);
|
||||
indexOps.delete();
|
||||
}
|
||||
|
||||
@Test // DATAES-68
|
||||
void shouldCallBeforeConvertCallback() {
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
SampleEntity saved = operations.save(entity);
|
||||
|
||||
assertThat(saved.getText()).isEqualTo("converted");
|
||||
}
|
||||
|
||||
@Test // DATAES-972
|
||||
@DisplayName("should apply conversion result to IndexQuery on save")
|
||||
void shouldApplyConversionResultToIndexQueryOnSave() {
|
||||
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
operations.save(entity);
|
||||
|
||||
ArgumentCaptor<IndexQuery> indexQueryCaptor = ArgumentCaptor.forClass(IndexQuery.class);
|
||||
verify(operations, times(2)).doIndex(indexQueryCaptor.capture(), any());
|
||||
|
||||
final IndexQuery capturedIndexQuery = indexQueryCaptor.getValue();
|
||||
SampleEntity convertedEntity = (SampleEntity) capturedIndexQuery.getObject();
|
||||
|
||||
final JoinField<String> joinField = convertedEntity.getJoinField();
|
||||
assertThat(joinField.getName()).isEqualTo("answer");
|
||||
assertThat(joinField.getParent()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getRouting()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getSeqNo()).isEqualTo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
}
|
||||
|
||||
@Test // DATAES-972
|
||||
@DisplayName("should apply conversion result to IndexQuery when not set ")
|
||||
void shouldApplyConversionResultToIndexQueryWhenNotSet() {
|
||||
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
final IndexQuery indexQuery = new IndexQuery();
|
||||
indexQuery.setId(entity.getId());
|
||||
indexQuery.setObject(entity);
|
||||
|
||||
operations.index(indexQuery, IndexCoordinates.of(INDEX));
|
||||
|
||||
ArgumentCaptor<IndexQuery> indexQueryCaptor = ArgumentCaptor.forClass(IndexQuery.class);
|
||||
verify(operations, times(2)).doIndex(indexQueryCaptor.capture(), any());
|
||||
|
||||
final IndexQuery capturedIndexQuery = indexQueryCaptor.getValue();
|
||||
SampleEntity convertedEntity = (SampleEntity) capturedIndexQuery.getObject();
|
||||
|
||||
final JoinField<String> joinField = convertedEntity.getJoinField();
|
||||
assertThat(joinField.getName()).isEqualTo("answer");
|
||||
assertThat(joinField.getParent()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getRouting()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getSeqNo()).isEqualTo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
}
|
||||
|
||||
@Test // DATAES-972
|
||||
@DisplayName("should not apply conversion result to IndexQuery when already set ")
|
||||
void shouldNotApplyConversionResultToIndexQueryWhenAlreadySet() {
|
||||
|
||||
SeqNoPrimaryTerm seqNoPrimaryTermOriginal = seqNoPrimaryTerm;
|
||||
seqNoPrimaryTerm = new SeqNoPrimaryTerm(7, 8);
|
||||
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
final IndexQuery indexQuery = new IndexQuery();
|
||||
indexQuery.setId(entity.getId());
|
||||
indexQuery.setObject(entity);
|
||||
indexQuery.setRouting("12");
|
||||
indexQuery.setSeqNo(seqNoPrimaryTermOriginal.getSequenceNumber());
|
||||
indexQuery.setPrimaryTerm(seqNoPrimaryTermOriginal.getPrimaryTerm());
|
||||
|
||||
operations.index(indexQuery, IndexCoordinates.of(INDEX));
|
||||
|
||||
ArgumentCaptor<IndexQuery> indexQueryCaptor = ArgumentCaptor.forClass(IndexQuery.class);
|
||||
verify(operations, times(2)).doIndex(indexQueryCaptor.capture(), any());
|
||||
|
||||
final IndexQuery capturedIndexQuery = indexQueryCaptor.getValue();
|
||||
SampleEntity convertedEntity = (SampleEntity) capturedIndexQuery.getObject();
|
||||
|
||||
final JoinField<String> joinField = convertedEntity.getJoinField();
|
||||
assertThat(joinField.getName()).isEqualTo("answer");
|
||||
assertThat(joinField.getParent()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getRouting()).isEqualTo("12");
|
||||
assertThat(capturedIndexQuery.getSeqNo()).isEqualTo(seqNoPrimaryTermOriginal.getSequenceNumber());
|
||||
assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTermOriginal.getPrimaryTerm());
|
||||
}
|
||||
|
||||
@Test // DATAES-972
|
||||
@DisplayName("should apply conversion result to IndexQuery in bulkIndex")
|
||||
void shouldApplyConversionResultToIndexQueryInBulkIndex() {
|
||||
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
final IndexQuery indexQuery = new IndexQuery();
|
||||
indexQuery.setId(entity.getId());
|
||||
indexQuery.setObject(entity);
|
||||
|
||||
operations.bulkIndex(Collections.singletonList(indexQuery), SampleEntity.class);
|
||||
|
||||
ArgumentCaptor<List<IndexQuery>> indexQueryListCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(operations, times(1)).bulkOperation(indexQueryListCaptor.capture(), any(), any());
|
||||
|
||||
final List<IndexQuery> capturedIndexQueries = indexQueryListCaptor.getValue();
|
||||
assertThat(capturedIndexQueries).hasSize(1);
|
||||
final IndexQuery capturedIndexQuery = capturedIndexQueries.get(0);
|
||||
SampleEntity convertedEntity = (SampleEntity) capturedIndexQuery.getObject();
|
||||
|
||||
final JoinField<String> joinField = convertedEntity.getJoinField();
|
||||
assertThat(joinField.getName()).isEqualTo("answer");
|
||||
assertThat(joinField.getParent()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getRouting()).isEqualTo("42");
|
||||
assertThat(capturedIndexQuery.getSeqNo()).isEqualTo(seqNoPrimaryTerm.getSequenceNumber());
|
||||
assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTerm.getPrimaryTerm());
|
||||
}
|
||||
|
||||
@Data
|
||||
@Document(indexName = INDEX)
|
||||
static class SampleEntity {
|
||||
@Id private String id;
|
||||
private String text;
|
||||
|
||||
@JoinTypeRelations(relations = { @JoinTypeRelation(parent = "question",
|
||||
children = { "answer" }) }) @Nullable private JoinField<String> joinField;
|
||||
|
||||
private SeqNoPrimaryTerm seqNoPrimaryTerm;
|
||||
|
||||
public SampleEntity(String id, String text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.event;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
abstract class ElasticsearchOperationsCallbackTest {
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Component
|
||||
static class SampleEntityBeforeConvertCallback implements BeforeConvertCallback<SampleEntity> {
|
||||
@Override
|
||||
public SampleEntity onBeforeConvert(SampleEntity entity, IndexCoordinates index) {
|
||||
entity.setText("converted");
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
IndexOperations indexOps = operations.indexOps(SampleEntity.class);
|
||||
indexOps.delete();
|
||||
indexOps.create();
|
||||
indexOps.putMapping(SampleEntity.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
IndexOperations indexOps = operations.indexOps(SampleEntity.class);
|
||||
indexOps.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallBeforeConvertCallback() {
|
||||
SampleEntity entity = new SampleEntity("1", "test");
|
||||
|
||||
SampleEntity saved = operations.save(entity);
|
||||
|
||||
assertThat(saved.getText()).isEqualTo("converted");
|
||||
}
|
||||
|
||||
@Document(indexName = "test-operations-callback")
|
||||
static class SampleEntity {
|
||||
@Id private String id;
|
||||
private String text;
|
||||
|
||||
public SampleEntity(String id, String text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,12 +16,11 @@
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class, ElasticsearchOperationsCallbackTest.Config.class })
|
||||
class ElasticsearchRestOperationsCallbackTest extends ElasticsearchOperationsCallbackTest {}
|
||||
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class,
|
||||
ElasticsearchOperationsCallbackIntegrationTest.Config.class })
|
||||
class ElasticsearchRestOperationsCallbackIntegrationTest extends ElasticsearchOperationsCallbackIntegrationTest {}
|
@ -16,12 +16,10 @@
|
||||
package org.springframework.data.elasticsearch.core.event;
|
||||
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class, ElasticsearchOperationsCallbackTest.Config.class })
|
||||
class ElasticsearchTransportOperationsCallbackTest extends ElasticsearchOperationsCallbackTest {}
|
||||
@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class, ElasticsearchOperationsCallbackIntegrationTest.Config.class })
|
||||
class ElasticsearchTransportOperationsCallbackIntegrationTest extends ElasticsearchOperationsCallbackIntegrationTest {}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.
|
||||
@ -30,6 +30,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.Integer;
|
||||
import java.lang.Object;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -45,6 +46,7 @@ import org.assertj.core.data.Percentage;
|
||||
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
@ -249,9 +251,29 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||
assertThat(fieldLastName.get("copy_to")).isEqualTo(copyToValue);
|
||||
}
|
||||
|
||||
@Test // DATAES-991
|
||||
@DisplayName("should write correct TermVector values")
|
||||
void shouldWriteCorrectTermVectorValues() {
|
||||
|
||||
IndexOperations indexOps = operations.indexOps(TermVectorFieldEntity.class);
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Xiao Yu
|
||||
*/
|
||||
@Test // #1767
|
||||
@DisplayName("should write dynamic mapping entries")
|
||||
void shouldWriteDynamicMappingEntries() {
|
||||
|
||||
IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class);
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
indexOps.delete();
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ -621,4 +643,40 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||
|
||||
@Field(type = FieldType.Rank_Features) private Map<String, Integer> topics;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Document(indexName = "termvectors-test")
|
||||
static class TermVectorFieldEntity {
|
||||
@Id private String id;
|
||||
@Field(type = FieldType.Text, termVector = TermVector.no) private String no;
|
||||
@Field(type = FieldType.Text, termVector = TermVector.yes) private String yes;
|
||||
@Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions;
|
||||
@Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets;
|
||||
@Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets;
|
||||
@Field(type = FieldType.Text,
|
||||
termVector = TermVector.with_positions_payloads) private String with_positions_payloads;
|
||||
@Field(type = FieldType.Text,
|
||||
termVector = TermVector.with_positions_offsets_payloads) private String with_positions_offsets_payloads;
|
||||
}
|
||||
|
||||
@Document(indexName = "dynamic-mapping")
|
||||
@DynamicMapping(DynamicMappingValue.False)
|
||||
static class DynamicMappingEntity {
|
||||
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
type = FieldType.Object) private Map<String, Object> objectMap;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
type = FieldType.Nested) private List<Map<String, Object>> nestedObjectMap;
|
||||
|
||||
@Nullable
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(Author author) {
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@ -410,19 +411,28 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
assertEquals(expected, mapping, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAES-148, #1767
|
||||
void shouldWriteDynamicMappingSettings() throws JSONException {
|
||||
|
||||
String expected = "{\n" + //
|
||||
" \"dynamic\": \"false\",\n" + //
|
||||
" \"properties\": {\n" + //
|
||||
" \"author\": {\n" + //
|
||||
" \"dynamic\": \"strict\",\n" + //
|
||||
" \"type\": \"object\",\n" + //
|
||||
" \"properties\": {}\n" + //
|
||||
" \"dynamic\": \"strict\",\n" + //
|
||||
" \"properties\": {\n" + //
|
||||
" }\n" + //
|
||||
" },\n" + //
|
||||
" \"objectMap\": {\n" + //
|
||||
" \"type\": \"object\",\n" + //
|
||||
" \"dynamic\": \"false\"\n" + //
|
||||
" },\n" + //
|
||||
" \"nestedObjectMap\": {\n" + //
|
||||
" \"type\": \"nested\",\n" + //
|
||||
" \"dynamic\": \"false\"\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
"}\n";
|
||||
"}"; //
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
|
||||
|
||||
@ -898,6 +908,10 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
static class ConfigureDynamicMappingEntity {
|
||||
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
type = FieldType.Object) private Map<String, Object> objectMap;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
type = FieldType.Nested) private List<Map<String, Object>> nestedObjectMap;
|
||||
|
||||
@Nullable
|
||||
public Author getAuthor() {
|
||||
|
@ -34,8 +34,9 @@ abstract class MappingContextBaseTests {
|
||||
|
||||
private SimpleElasticsearchMappingContext setupMappingContext() {
|
||||
|
||||
SimpleElasticsearchMappingContext mappingContext = new ElasticsearchConfigurationSupport()
|
||||
.elasticsearchMappingContext();
|
||||
ElasticsearchConfigurationSupport configurationSupport = new ElasticsearchConfigurationSupport();
|
||||
SimpleElasticsearchMappingContext mappingContext = configurationSupport
|
||||
.elasticsearchMappingContext(configurationSupport.elasticsearchCustomConversions());
|
||||
mappingContext.initialize();
|
||||
return mappingContext;
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.mapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.elasticsearch.core.query.Query;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Test that a whole entity can be converted using custom conversions
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { EntityCustomConversionIntegrationTests.Config.class })
|
||||
public class EntityCustomConversionIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.core.mapping" },
|
||||
considerNestedRepositories = true)
|
||||
static class Config extends ElasticsearchRestTemplateConfiguration {
|
||||
@Override
|
||||
public ElasticsearchCustomConversions elasticsearchCustomConversions() {
|
||||
return new ElasticsearchCustomConversions(Arrays.asList(new EntityToMapConverter(), new MapToEntityConverter()));
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
IndexOperations indexOps = operations.indexOps(Entity.class);
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
operations.indexOps(Entity.class).delete();
|
||||
}
|
||||
|
||||
@Test // #1667
|
||||
@DisplayName("should use CustomConversions on entity")
|
||||
void shouldUseCustomConversionsOnEntity() {
|
||||
|
||||
Entity entity = Entity.builder() //
|
||||
.value("hello") //
|
||||
.location(GeoJsonPoint.of(8.0, 42.7)) //
|
||||
.build();
|
||||
|
||||
org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document
|
||||
.create();
|
||||
operations.getElasticsearchConverter().write(entity, document);
|
||||
|
||||
assertThat(document.getString("the_value")).isEqualTo("hello");
|
||||
assertThat(document.getString("the_lon")).isEqualTo("8.0");
|
||||
assertThat(document.getString("the_lat")).isEqualTo("42.7");
|
||||
}
|
||||
|
||||
@Test // #1667
|
||||
@DisplayName("should store and load entity from Elasticsearch")
|
||||
void shouldStoreAndLoadEntityFromElasticsearch() {
|
||||
|
||||
Entity entity = Entity.builder() //
|
||||
.value("hello") //
|
||||
.location(GeoJsonPoint.of(8.0, 42.7)) //
|
||||
.build();
|
||||
|
||||
Entity savedEntity = operations.save(entity);
|
||||
operations.indexOps(Entity.class).refresh();
|
||||
|
||||
SearchHits<Entity> searchHits = operations.search(Query.findAll(), Entity.class);
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
Entity foundEntity = searchHits.getSearchHit(0).getContent();
|
||||
assertThat(foundEntity).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "entity-with-custom-conversions")
|
||||
static class Entity {
|
||||
private String value;
|
||||
private GeoJsonPoint location;
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
static class EntityToMapConverter implements Converter<Entity, Map<String, Object>> {
|
||||
@Override
|
||||
public Map<String, Object> convert(Entity source) {
|
||||
LinkedHashMap<String, Object> target = new LinkedHashMap<>();
|
||||
target.put("the_value", source.getValue());
|
||||
target.put("the_lat", "" + source.getLocation().getY());
|
||||
target.put("the_lon", "" + source.getLocation().getX());
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
static class MapToEntityConverter implements Converter<Map<String, Object>, Entity> {
|
||||
|
||||
@Override
|
||||
public Entity convert(Map<String, Object> source) {
|
||||
Entity entity = new Entity();
|
||||
entity.setValue((String) source.get("the_value"));
|
||||
entity.setLocation(GeoJsonPoint.of( //
|
||||
Double.parseDouble((String) (source.get("the_lon"))), //
|
||||
Double.parseDouble((String) (source.get("the_lat"))) //
|
||||
));
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
@ -192,20 +192,6 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
|
||||
assertThat(seqNoProperty.isReadable()).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATAES-828
|
||||
void shouldRequireFormatForDateField() {
|
||||
assertThatExceptionOfType(MappingException.class) //
|
||||
.isThrownBy(() -> context.getRequiredPersistentEntity(DateFieldWithNoFormat.class)) //
|
||||
.withMessageContaining("date");
|
||||
}
|
||||
|
||||
@Test // DATAES-828
|
||||
void shouldRequireFormatForDateNanosField() {
|
||||
assertThatExceptionOfType(MappingException.class) //
|
||||
.isThrownBy(() -> context.getRequiredPersistentEntity(DateNanosFieldWithNoFormat.class)) //
|
||||
.withMessageContaining("date");
|
||||
}
|
||||
|
||||
@Test // DATAES-924
|
||||
@DisplayName("should require pattern for custom date format")
|
||||
void shouldRequirePatternForCustomDateFormat() {
|
||||
|
@ -68,9 +68,9 @@ public class ElasticsearchRestTemplateConfiguration extends AbstractElasticsearc
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) {
|
||||
RestHighLevelClient client = elasticsearchClient();
|
||||
return new ElasticsearchRestTemplate(client, elasticsearchConverter) {
|
||||
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
|
||||
RestHighLevelClient elasticsearchClient) {
|
||||
return new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter) {
|
||||
@Override
|
||||
public <T> T execute(ClientCallback<T> callback) {
|
||||
try {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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.
|
||||
@ -151,6 +151,7 @@ public abstract class CustomMethodRepositoryBaseTests {
|
||||
// then
|
||||
assertThat(page).isNotNull();
|
||||
assertThat(page.getTotalElements()).isGreaterThanOrEqualTo(1L);
|
||||
assertThat(page.getContent().get(0)).isInstanceOf(SampleEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1570,6 +1571,35 @@ public abstract class CustomMethodRepositoryBaseTests {
|
||||
assertThat((nextPageable.getPageNumber())).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // #1811
|
||||
void shouldReturnSearchPageWithQuery() {
|
||||
List<SampleEntity> entities = createSampleEntities("abc", 20);
|
||||
repository.saveAll(entities);
|
||||
|
||||
SearchPage<SampleEntity> searchPage = repository.searchWithQueryByMessage("Message", PageRequest.of(0, 10));
|
||||
|
||||
assertThat(searchPage).isNotNull();
|
||||
SearchHits<SampleEntity> searchHits = searchPage.getSearchHits();
|
||||
assertThat(searchHits).isNotNull();
|
||||
assertThat((searchHits.getTotalHits())).isEqualTo(20);
|
||||
assertThat(searchHits.getSearchHits()).hasSize(10);
|
||||
Pageable nextPageable = searchPage.nextPageable();
|
||||
assertThat((nextPageable.getPageNumber())).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // #1917
|
||||
void shouldReturnAllDocumentsWithUnpagedQuery() {
|
||||
|
||||
List<SampleEntity> entities = createSampleEntities("abc", 20);
|
||||
repository.saveAll(entities);
|
||||
|
||||
SearchHits<SampleEntity> searchHits = repository.searchWithQueryByMessageUnpaged("Message");
|
||||
|
||||
assertThat(searchHits).isNotNull();
|
||||
assertThat((searchHits.getTotalHits())).isEqualTo(20);
|
||||
assertThat(searchHits.getSearchHits()).hasSize(20);
|
||||
}
|
||||
|
||||
private List<SampleEntity> createSampleEntities(String type, int numberOfEntities) {
|
||||
|
||||
List<SampleEntity> entities = new ArrayList<>();
|
||||
@ -1746,6 +1776,12 @@ public abstract class CustomMethodRepositoryBaseTests {
|
||||
SearchHits<SampleEntity> searchBy(Sort sort);
|
||||
|
||||
SearchPage<SampleEntity> searchByMessage(String message, Pageable pageable);
|
||||
|
||||
@Query("{\"match\": {\"message\": \"?0\"}}")
|
||||
SearchPage<SampleEntity> searchWithQueryByMessage(String message, Pageable pageable);
|
||||
|
||||
@Query("{\"match\": {\"message\": \"?0\"}}")
|
||||
SearchHits<SampleEntity> searchWithQueryByMessageUnpaged(String message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
public class ElasticsearchStringQueryUnitTestBase {
|
||||
|
||||
protected ElasticsearchConverter setupConverter() {
|
||||
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(
|
||||
new SimpleElasticsearchMappingContext());
|
||||
Collection<Converter<?, ?>> converters = new ArrayList<>();
|
||||
converters.add(ElasticsearchStringQueryUnitTests.CarConverter.INSTANCE);
|
||||
CustomConversions customConversions = new ElasticsearchCustomConversions(converters);
|
||||
converter.setConversions(customConversions);
|
||||
converter.afterPropertiesSet();
|
||||
return converter;
|
||||
}
|
||||
|
||||
static class Car {
|
||||
@Nullable private String name;
|
||||
@Nullable private String model;
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(@Nullable String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(@Nullable String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
enum CarConverter implements Converter<Car, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(ElasticsearchStringQueryUnitTests.Car car) {
|
||||
return (car.getName() != null ? car.getName() : "null") + '-'
|
||||
+ (car.getModel() != null ? car.getModel() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@ -24,6 +25,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -31,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
@ -43,9 +46,7 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
@ -55,16 +56,16 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Niklas Herder
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ElasticsearchStringQueryUnitTests {
|
||||
public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase {
|
||||
|
||||
@Mock ElasticsearchOperations operations;
|
||||
ElasticsearchConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
}
|
||||
|
||||
@Test // DATAES-552
|
||||
@ -88,7 +89,53 @@ public class ElasticsearchStringQueryUnitTests {
|
||||
.isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)");
|
||||
}
|
||||
|
||||
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args)
|
||||
@Test // #1790
|
||||
@DisplayName("should escape Strings in query parameters")
|
||||
void shouldEscapeStringsInQueryParameters() throws Exception {
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\"");
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource())
|
||||
.isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}");
|
||||
}
|
||||
|
||||
@Test // #1858
|
||||
@DisplayName("should only quote String query parameters")
|
||||
void shouldOnlyEscapeStringQueryParameters() throws Exception {
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAge", Integer.valueOf(30));
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource()).isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : 30 } } } }");
|
||||
|
||||
}
|
||||
|
||||
@Test // #1858
|
||||
@DisplayName("should only quote String collection query parameters")
|
||||
void shouldOnlyEscapeStringCollectionQueryParameters() throws Exception {
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAgeIn",
|
||||
new ArrayList<>(Arrays.asList(30, 35, 40)));
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource())
|
||||
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : [30,35,40] } } } }");
|
||||
|
||||
}
|
||||
|
||||
@Test // #1858
|
||||
@DisplayName("should escape Strings in collection query parameters")
|
||||
void shouldEscapeStringsInCollectionsQueryParameters() throws Exception {
|
||||
|
||||
final List<String> another_string = Arrays.asList("hello \"Stranger\"", "Another string");
|
||||
List<String> params = new ArrayList<>(another_string);
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByNameIn", params);
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource()).isEqualTo(
|
||||
"{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"hello \\\"Stranger\\\"\",\"Another string\"] } } } }");
|
||||
}
|
||||
|
||||
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||
@ -97,6 +144,21 @@ public class ElasticsearchStringQueryUnitTests {
|
||||
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
|
||||
}
|
||||
|
||||
@Test // #1866
|
||||
@DisplayName("should use converter on parameters")
|
||||
void shouldUseConverterOnParameters() throws NoSuchMethodException {
|
||||
|
||||
Car car = new Car();
|
||||
car.setName("Toyota");
|
||||
car.setModel("Prius");
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByCar", car);
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource())
|
||||
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }");
|
||||
}
|
||||
|
||||
private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) {
|
||||
return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery());
|
||||
}
|
||||
@ -105,33 +167,59 @@ public class ElasticsearchStringQueryUnitTests {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory(), converter.getMappingContext());
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<Person, String> {
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }")
|
||||
List<Person> findByAge(Integer age);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }")
|
||||
List<Person> findByAgeIn(ArrayList<Integer> age);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
|
||||
Person findByName(String name);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'terms' : { 'name' : ?0 } } } }")
|
||||
Person findByNameIn(ArrayList<String> names);
|
||||
|
||||
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
|
||||
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
|
||||
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
|
||||
|
||||
@Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}")
|
||||
SearchHits<Book> findByPrefix(String prefix);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }")
|
||||
Person findByCar(Car car);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
* @author Niklas Herder
|
||||
*/
|
||||
|
||||
@Document(indexName = "test-index-person-query-unittest", replicas = 0, refreshInterval = "-1")
|
||||
static class Person {
|
||||
|
||||
@Nullable public int age;
|
||||
@Nullable @Id private String id;
|
||||
@Nullable private String name;
|
||||
@Nullable @Field(type = FieldType.Nested) private List<Car> car;
|
||||
@Nullable @Field(type = FieldType.Nested, includeInParent = true) private List<Book> books;
|
||||
|
||||
@Nullable
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
@ -191,38 +279,6 @@ public class ElasticsearchStringQueryUnitTests {
|
||||
searchAnalyzer = "standard") }) private String description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
static class Car {
|
||||
|
||||
private String name;
|
||||
private String model;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,6 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@ -34,6 +35,7 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
@ -46,9 +48,7 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.query.StringQuery;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
@ -62,16 +62,15 @@ import org.springframework.lang.Nullable;
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase {
|
||||
|
||||
SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
ElasticsearchConverter converter;
|
||||
|
||||
@Mock ReactiveElasticsearchOperations operations;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
|
||||
when(operations.getElasticsearchConverter()).thenReturn(setupConverter());
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
@ -124,7 +123,33 @@ public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
.isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)");
|
||||
}
|
||||
|
||||
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args)
|
||||
@Test // #1790
|
||||
@DisplayName("should escape Strings in query parameters")
|
||||
void shouldEscapeStringsInQueryParameters() throws Exception {
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\"");
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource())
|
||||
.isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}");
|
||||
}
|
||||
|
||||
@Test // #1866
|
||||
@DisplayName("should use converter on parameters")
|
||||
void shouldUseConverterOnParameters() throws Exception {
|
||||
|
||||
Car car = new Car();
|
||||
car.setName("Toyota");
|
||||
car.setModel("Prius");
|
||||
|
||||
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByCar", car);
|
||||
|
||||
assertThat(query).isInstanceOf(StringQuery.class);
|
||||
assertThat(((StringQuery) query).getSource())
|
||||
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }");
|
||||
}
|
||||
|
||||
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
|
||||
@ -144,7 +169,7 @@ public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new ReactiveElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory(), converter.getMappingContext());
|
||||
new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext());
|
||||
}
|
||||
|
||||
private ReactiveElasticsearchStringQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
|
||||
@ -168,6 +193,13 @@ public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
|
||||
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
|
||||
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
|
||||
|
||||
@Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}")
|
||||
Flux<SearchHit<Book>> findByPrefix(String prefix);
|
||||
|
||||
@Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }")
|
||||
Mono<Person> findByCar(Car car);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,42 +278,6 @@ public class ReactiveElasticsearchStringQueryUnitTests {
|
||||
searchAnalyzer = "standard") }) private String description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Artur Konczak
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
static class Car {
|
||||
|
||||
private String name;
|
||||
private String model;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
*/
|
||||
static class Author {
|
||||
|
||||
@Nullable private String id;
|
||||
|
@ -31,6 +31,7 @@ import java.lang.Boolean;
|
||||
import java.lang.Long;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.elasticsearch.ElasticsearchStatusException;
|
||||
@ -515,16 +516,38 @@ public class SimpleReactiveElasticsearchRepositoryTests {
|
||||
}
|
||||
|
||||
@Test // DATAES-519
|
||||
public void annotatedFinderMethodShouldBeExecutedCorrectly() {
|
||||
void annotatedFinderMethodShouldBeExecutedCorrectly() {
|
||||
|
||||
bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), //
|
||||
SampleEntity.builder().id("id-two").message("test message").build(), //
|
||||
SampleEntity.builder().id("id-three").message("test test").build()) //
|
||||
.block();
|
||||
int count = 30;
|
||||
SampleEntity[] sampleEntities = IntStream.range(1, count + 1)
|
||||
.mapToObj(i -> SampleEntity.builder().id("id-" + i).message("test " + i).build()).collect(Collectors.toList())
|
||||
.toArray(new SampleEntity[count]);
|
||||
|
||||
bulkIndex(sampleEntities).block();
|
||||
|
||||
repository.findAllViaAnnotatedQueryByMessageLike("test") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(2) //
|
||||
.expectNextCount(count) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1917
|
||||
void annotatedFinderMethodPagedShouldBeExecutedCorrectly() {
|
||||
|
||||
int count = 30;
|
||||
SampleEntity[] sampleEntities = IntStream.range(1, count + 1)
|
||||
.mapToObj(i -> SampleEntity.builder().id("id-" + i).message("test " + i).build()).collect(Collectors.toList())
|
||||
.toArray(new SampleEntity[count]);
|
||||
|
||||
bulkIndex(sampleEntities).block();
|
||||
|
||||
repository.findAllViaAnnotatedQueryByMessageLikePaged("test", PageRequest.of(0, 20)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(20) //
|
||||
.verifyComplete();
|
||||
repository.findAllViaAnnotatedQueryByMessageLikePaged("test", PageRequest.of(1, 20)) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(10) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ -572,6 +595,9 @@ public class SimpleReactiveElasticsearchRepositoryTests {
|
||||
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"message\" : \"?0\" } } } }")
|
||||
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageLike(String message);
|
||||
|
||||
@Query("{ \"bool\" : { \"must\" : { \"term\" : { \"message\" : \"?0\" } } } }")
|
||||
Flux<SampleEntity> findAllViaAnnotatedQueryByMessageLikePaged(String message, Pageable pageable);
|
||||
|
||||
Mono<SampleEntity> findFirstByMessageLike(String message);
|
||||
|
||||
Mono<Long> countAllByMessage(String message);
|
||||
|
Loading…
x
Reference in New Issue
Block a user