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

Compare commits

...

34 Commits

Author SHA1 Message Date
Mark Paluch bf3248b536 Release version 4.3.3 (2021.1.3).
See #2091
2022-03-21 14:58:52 +01:00
Mark Paluch c59bb0b434 Prepare 4.3.3 (2021.1.3).
See #2091
2022-03-21 14:58:28 +01:00
Peter-Josef Meisch d93d1e0738 Set visibility SimpleElasticsearchPersistentEntity.ContextConfiguration to public.
Original Pull Request #2116
Closes #2114

(cherry picked from commit 709b4c615e)
2022-03-21 09:31:08 +01:00
Mark Paluch aebdc8f86b Use Java 8 to build snapshots for Artifactory.
Closes #2095
2022-03-15 14:20:01 +01:00
Peter-Josef Meisch 2be27593d6 MappingBuilder must set configured date formats for date_range fields.
Original Pull Request #2103
Closes #2102

(cherry picked from commit bf080002bc)
2022-02-22 21:13:43 +01:00
Mark Paluch aa22d8239d Update CI properties.
See #2091
2022-02-22 14:09:14 +01:00
Mark Paluch 6746bc5278 Upgrade to Maven Wrapper 3.8.4.
See #2100
2022-02-22 13:56:16 +01:00
Mark Paluch a6cb959605 Polishing.
Fix Javadoc errors.

See #2095
2022-02-22 09:56:23 +01:00
Mark Paluch e04905a1a7 Use Java 17 to build snapshots for Artifactory.
Closes #2095
2022-02-22 08:53:04 +01:00
Peter-Josef Meisch 834b10f578 Remove blocking code in SearchDocument processing.
Original Pull Request #2094
Closes #2025

(cherry picked from commit c1a1ea9724)
2022-02-20 13:34:49 +01:00
Peter-Josef Meisch 823cfa919a keep the documentation change from the cherrypick 2022-02-18 20:51:46 +01:00
Peter-Josef Meisch ab29ae4219 Documentation about compatibility headers.
Original Pull Request #2093
Closes #2088

(cherry picked from commit cf380e289d)
2022-02-18 20:36:51 +01:00
Mark Paluch 62fb89208a After release cleanups.
See #2056
2022-02-18 10:49:02 +01:00
Mark Paluch e8c3badc56 Prepare next development iteration.
See #2056
2022-02-18 10:49:00 +01:00
Mark Paluch 187c9bbd5d Release version 4.3.2 (2021.1.2).
See #2056
2022-02-18 10:41:01 +01:00
Mark Paluch e1a5811406 Prepare 4.3.2 (2021.1.2).
See #2056
2022-02-18 10:40:40 +01:00
Greg L. Turnquist b643669d36 Update CI properties.
See #2056
2022-02-14 14:39:49 -06:00
Peter-Josef Meisch f66af53480 Fix exception on property conversion with criteria exists/empty/non-empty.
Original Pull Request #2081
Closes #2080

(cherry picked from commit 32fa7391c4)
2022-02-09 23:25:34 +01:00
Mark Paluch 14099970bb Polishing.
Extract Docker and Artifactory credentials into properties file.

See #2074
2022-02-04 15:18:50 +01:00
Greg L. Turnquist 0ab253422f Externalize build properties.
See #2074.
2022-02-04 15:18:48 +01:00
Christoph Strobl 0d2a6b98e8 After release cleanups.
See #1987
2022-01-14 10:45:02 +01:00
Christoph Strobl 30602496bd Prepare next development iteration.
See #1987
2022-01-14 10:44:59 +01:00
Christoph Strobl 95762b4fde Release version 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:59 +01:00
Christoph Strobl 0771e90031 Prepare 4.3.1 (2021.1.1).
See #1987
2022-01-14 10:28:27 +01:00
Peter-Josef Meisch 6729330500 Update to log4j 2.17.0 2021-12-18 20:34:28 +01:00
Peter-Josef Meisch 8796292611 update log4j dependency version 2021-12-14 13:53:59 +01:00
Peter-Josef Meisch f3f9ca4002 Fix FieldType mapping.
Original PullRequest #2026
Closes #2024

(cherry picked from commit f7a6a97c4e)
2021-12-13 21:52:28 +01:00
Peter-Josef Meisch 083a38ed57 Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369a)
2021-12-05 09:02:06 +01:00
Sascha Woo 4f3aa52958 Fix IndexOutOfBoundsException when try to map inner hits with no results returned.
Original Pull Request #1998
Closes #1997
Co-authored-by: Peter-Josef Meisch <pj.meisch@sothawo.com>

(cherry picked from commit 49324a369a)
2021-11-23 20:26:14 +01:00
Peter-Josef Meisch 3256a2bfe0 Fix RestStatusException cause.
Original Pull Request #1996
Closes #1995

(cherry picked from commit 45b4c99e95)
2021-11-16 07:38:01 +01:00
Peter-Josef Meisch 95401a5bd7 Exclude commons-logging dependency from Elasticsearch dependencies.
Original Pull Request #1993
Closes #1989
2021-11-13 14:56:57 +01:00
Mark Paluch 12cd64cfc8 Update build trigger to use branch build.
See #1965
2021-11-12 14:31:54 +01:00
Jens Schauder 7e557317d1 After release cleanups.
See #1965
2021-11-12 11:00:07 +01:00
Jens Schauder c9846ab8ad Prepare next development iteration.
See #1965
2021-11-12 11:00:05 +01:00
37 changed files with 844 additions and 317 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
#Mon Oct 11 14:30:32 CEST 2021
#Tue Feb 22 13:56:16 CET 2022
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
Vendored
+27 -49
View File
@@ -1,9 +1,15 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
}
pipeline {
agent none
triggers {
pollSCM 'H/10 * * * *'
upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
upstream(upstreamProjects: "spring-data-commons/2.6.x", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -12,7 +18,7 @@ pipeline {
}
stages {
stage("test: baseline (jdk8)") {
stage("test: baseline (main)") {
when {
anyOf {
branch 'main'
@@ -25,14 +31,14 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
@@ -50,23 +56,23 @@ pipeline {
}
}
parallel {
stage("test: baseline (jdk11)") {
stage("test: baseline (next)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/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') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
@@ -74,23 +80,23 @@ pipeline {
}
}
stage("test: baseline (jdk17)") {
stage("test: baseline (LTS)") {
agent {
label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
environment {
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
DOCKER_HUB = credentials("${p['docker.credentials']}")
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('openjdk:17-bullseye').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.lts.image']).inside(p['docker.java.inside.docker']) {
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
sh 'PROFILE=java11 ci/verify.sh'
sh 'PROFILE=none ci/verify.sh'
sh "ci/clean.sh"
}
}
@@ -113,13 +119,13 @@ pipeline {
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
@@ -133,34 +139,6 @@ pipeline {
}
}
}
stage('Publish documentation') {
when {
branch 'main'
}
agent {
label 'data'
}
options { timeout(time: 20, unit: 'MINUTES') }
environment {
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
}
steps {
script {
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
"-Dartifactory.distribution-repository=temp-private-local " +
'-Dmaven.test.skip=true clean deploy -U -B'
}
}
}
}
}
}
post {
+29
View File
@@ -0,0 +1,29 @@
# Java versions
java.main.tag=8u322-b06-jdk
java.next.tag=11.0.14.1_1-jdk
java.lts.tag=17.0.2_8-jdk
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
docker.java.next.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.next.tag}
docker.java.lts.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.lts.tag}
# Supported versions of MongoDB
docker.mongodb.4.0.version=4.0.28
docker.mongodb.4.4.version=4.4.12
docker.mongodb.5.0.version=5.0.6
# Supported versions of Redis
docker.redis.6.version=6.2.6
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.12
# Docker environment settings
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
# Credentials
docker.registry=
docker.credentials=hub.docker.com-springbuildmaster
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
+19 -5
View File
@@ -5,12 +5,12 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.3.0</version>
<version>4.3.3</version>
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.6.0</version>
<version>2.6.3</version>
</parent>
<name>Spring Data Elasticsearch</name>
@@ -19,9 +19,9 @@
<properties>
<elasticsearch>7.15.2</elasticsearch>
<log4j>2.14.1</log4j>
<log4j>2.17.0</log4j>
<netty>4.1.65.Final</netty>
<springdata.commons>2.6.0</springdata.commons>
<springdata.commons>2.6.3</springdata.commons>
<testcontainers>1.15.3</testcontainers>
<blockhound-junit>1.0.6.RELEASE</blockhound-junit>
<java-module-name>spring.data.elasticsearch</java-module-name>
@@ -145,6 +145,12 @@
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -152,6 +158,12 @@
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@@ -461,7 +473,9 @@
</module>
</checkstyleRules>
<includes>**/*</includes>
<excludes>.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy</excludes>
<excludes>
.git/**/*,target/**/*,**/target/**/*,.idea/**/*,**/spring.schemas,**/*.svg,mvnw,mvnw.cmd,**/*.policy
</excludes>
<sourceDirectories>./</sourceDirectories>
</configuration>
</plugin>
@@ -199,6 +199,29 @@ Default is 5 sec.
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens.
If this is used in the reactive setup, the supplier function *must not* block!
=== Elasticsearch 7 compatibility headers
When using Spring Data Elasticsearch 4 - which uses the Elasticsearch 7 client libraries - and accessing an Elasticsearch cluster that is running on version 8, it is necessary to set the compatibility headers
https://www.elastic.co/guide/en/elasticsearch/reference/8.0/rest-api-compatibility.html[see Elasticserach documentation].
This should be done using a header supplier like shown above:
====
[source,java]
----
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
configurationBuilder //
// ...
.withHeaders(() -> {
HttpHeaders defaultCompatibilityHeaders = new HttpHeaders();
defaultCompatibilityHeaders.add("Accept",
"application/vnd.elasticsearch+json;compatible-with=7");
defaultCompatibilityHeaders.add("Content-Type",
"application/vnd.elasticsearch+json;compatible-with=7");
return defaultCompatibilityHeaders;
});
----
====
[[elasticsearch.clients.logging]]
== Client Logging
@@ -46,6 +46,16 @@ public @interface CompletionContext {
* @since 4.3
*/
enum ContextMappingType {
CATEGORY, GEO
CATEGORY("category"), GEO("geo");
private final String mappedName;
ContextMappingType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
}
@@ -44,7 +44,7 @@ public @interface Document {
* Name of the Elasticsearch index.
* <ul>
* <li>Lowercase only</li>
* <li>Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #</li>
* <li>Cannot include \, /, *, ?, ", &gt;, &lt;, |, ` ` (space character), ,, #</li>
* <li>Cannot start with -, _, +</li>
* <li>Cannot be . or ..</li>
* <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit
@@ -17,7 +17,7 @@ package org.springframework.data.elasticsearch.annotations;
/**
* Values for the {@code dynamic} mapping parameter.
*
*
* @author Sascha Woo
* @since 4.3
*/
@@ -25,26 +25,36 @@ public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE,
TRUE("true"),
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME,
RUNTIME("runtime"),
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE,
FALSE("false"),
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT,
STRICT("strict"),
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT
INHERIT("nherit");
private final String mappedName;
Dynamic(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -17,13 +17,23 @@ package org.springframework.data.elasticsearch.annotations;
/**
* values for the {@link DynamicMapping annotation}
*
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True, False, Strict
True("true"), False("false"), Strict("strict");
private final String mappedName;
DynamicMappingValue(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -26,40 +26,51 @@ package org.springframework.data.elasticsearch.annotations;
* @author Morgan Lutz
*/
public enum FieldType {
Auto, //
Text, //
Keyword, //
Long, //
Integer, //
Short, //
Byte, //
Double, //
Float, //
Half_Float, //
Scaled_Float, //
Date, //
Date_Nanos, //
Boolean, //
Binary, //
Integer_Range, //
Float_Range, //
Long_Range, //
Double_Range, //
Date_Range, //
Ip_Range, //
Object, //
Nested, //
Ip, //
TokenCount, //
Percolator, //
Flattened, //
Search_As_You_Type, //
Auto("auto"), //
Text("text"), //
Keyword("keyword"), //
Long("long"), //
Integer("integer"), //
Short("short"), //
Byte("byte"), //
Double("double"), //
Float("float"), //
Half_Float("half_float"), //
Scaled_Float("scaled_float"), //
Date("date"), //
Date_Nanos("date_nanos"), //
Boolean("boolean"), //
Binary("binary"), //
Integer_Range("integer_range"), //
Float_Range("float_range"), //
Long_Range("long_range"), //
Double_Range("double_range"), //
Date_Range("date_range"), //
Ip_Range("ip_range"), //
Object("object"), //
Nested("nested"), //
Ip("ip"), //
TokenCount("token_count"), //
Percolator("percolator"), //
Flattened("flattened"), //
Search_As_You_Type("search_as_you_type"), //
/** @since 4.1 */
Rank_Feature, //
Rank_Feature("rank_feature"), //
/** @since 4.1 */
Rank_Features, //
Rank_Features("rank_features"), //
/** since 4.2 */
Wildcard, //
Wildcard("wildcard"), //
/** @since 4.2 */
Dense_Vector //
Dense_Vector("dense_vector") //
;
private final String mappedName;
FieldType(String mappedName) {
this.mappedName = mappedName;
}
public String getMappedName() {
return mappedName;
}
}
@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -109,7 +110,8 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
List<SearchHits<T>> res = new ArrayList<>(queries.size());
int c = 0;
for (Query query : queries) {
res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), documentCallback::doWith)));
res.add(
callback.doWith(SearchDocumentResponse.from(items[c++].getResponse(), getEntityCreator(documentCallback))));
}
return res;
}
@@ -142,7 +144,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
@@ -175,7 +177,7 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
index);
SearchResponse response = items[c++].getResponse();
res.add(callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith)));
res.add(callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback))));
}
return res;
}
@@ -215,5 +217,9 @@ public abstract class AbstractElasticsearchRestTransportTemplate extends Abstrac
return suggest(suggestion, getIndexCoordinatesFor(clazz));
}
protected <T> SearchDocumentResponse.EntityCreator<T> getEntityCreator(ReadDocumentCallback<T> documentCallback) {
return searchDocument -> CompletableFuture.completedFuture(documentCallback.doWith(searchDocument));
}
// endregion
}
@@ -64,9 +64,9 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
}
if (elasticsearchException instanceof ElasticsearchStatusException) {
ElasticsearchStatusException restStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(restStatusException.status().getStatus(), restStatusException.getMessage(),
restStatusException.getCause());
ElasticsearchStatusException elasticsearchStatusException = (ElasticsearchStatusException) elasticsearchException;
return new RestStatusException(elasticsearchStatusException.status().getStatus(),
elasticsearchStatusException.getMessage(), elasticsearchStatusException);
}
return new UncategorizedElasticsearchException(ex.getMessage(), ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 the original author or authors.
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -319,7 +319,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -336,7 +336,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -351,7 +351,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -378,8 +378,8 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTranspor
Assert.isTrue(items.length == request.requests().size(), "Response should has same length with queries");
return items;
}
// endregion
// endregion
// region ClientCallback
/**
* Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on
@@ -354,7 +354,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchHits<T>> callback = new ReadSearchDocumentResponseCallback<>(clazz, index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -372,7 +372,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -389,7 +389,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTem
ReadDocumentCallback<T> documentCallback = new ReadDocumentCallback<T>(elasticsearchConverter, clazz, index);
SearchDocumentResponseCallback<SearchScrollHits<T>> callback = new ReadSearchScrollDocumentResponseCallback<>(clazz,
index);
return callback.doWith(SearchDocumentResponse.from(response, documentCallback::doWith));
return callback.doWith(SearchDocumentResponse.from(response, getEntityCreator(documentCallback)));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2021 the original author or authors.
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.elasticsearch.Version;
@@ -771,15 +770,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
});
}
private Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
private <T> Mono<SearchDocumentResponse> doFindForResponse(Query query, Class<?> clazz, IndexCoordinates index) {
return Mono.defer(() -> {
SearchRequest request = requestFactory.searchRequest(query, clazz, index);
request = prepareSearchRequest(request, false);
SearchDocumentCallback<?> documentCallback = new ReadSearchDocumentCallback<>(clazz, index);
// noinspection unchecked
SearchDocumentResponse.EntityCreator<T> entityCreator = searchDocument -> ((Mono<T>) documentCallback
.toEntity(searchDocument)).toFuture();
return doFindForResponse(request, searchDocument -> documentCallback.toEntity(searchDocument).block());
return doFindForResponse(request, entityCreator);
});
}
@@ -896,19 +898,18 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
* Customization hook on the actual execution result {@link Mono}. <br />
*
* @param request the already prepared {@link SearchRequest} ready to be executed.
* @param suggestEntityCreator
* @param entityCreator
* @return a {@link Mono} emitting the result of the operation converted to s {@link SearchDocumentResponse}.
*/
protected Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
Function<SearchDocument, ? extends Object> suggestEntityCreator) {
protected <T> Mono<SearchDocumentResponse> doFindForResponse(SearchRequest request,
SearchDocumentResponse.EntityCreator<T> entityCreator) {
if (QUERY_LOGGER.isDebugEnabled()) {
QUERY_LOGGER.debug("Executing doFindForResponse: {}", request);
}
return Mono.from(execute(client1 -> client1.searchForResponse(request))).map(searchResponse -> {
return SearchDocumentResponse.from(searchResponse, suggestEntityCreator);
});
return Mono.from(execute(client -> client.searchForResponse(request)))
.map(searchResponse -> SearchDocumentResponse.from(searchResponse, entityCreator));
}
/**
@@ -123,7 +123,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param <T>
@@ -137,7 +136,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
@@ -150,7 +148,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param index the target index, must not be {@literal null}
@@ -165,7 +162,6 @@ public interface ReactiveSearchOperations {
/**
* Search the index for entities matching the given {@link Query query}.
*
* @param <T>
* @param query must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @param resultType the projection result type.
@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.NestedMetaData;
@@ -45,6 +46,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Roman Puchkovskiy
* @author Matt Gilene
* @author Sascha Woo
* @since 4.0
*/
class SearchHitMapping<T> {
@@ -194,7 +196,7 @@ class SearchHitMapping<T> {
*/
private SearchHits<?> mapInnerDocuments(SearchHits<SearchDocument> searchHits, Class<T> type) {
if (searchHits.getTotalHits() == 0) {
if (searchHits.isEmpty()) {
return searchHits;
}
@@ -239,7 +241,7 @@ class SearchHitMapping<T> {
searchHits.getSuggest());
}
} catch (Exception e) {
LOGGER.warn("Could not map inner_hits", e);
throw new UncategorizedElasticsearchException("Unable to convert inner hits.", e);
}
return searchHits;
@@ -76,7 +76,10 @@ public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPro
public Object write(Object value) {
Assert.notNull(value, "value must not be null.");
Assert.isInstanceOf(Range.class, value, "value must be instance of Range.");
if (!Range.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
Range<T> range = (Range<T>) value;
@@ -58,6 +58,10 @@ public class DatePropertyValueConverter extends AbstractPropertyValueConverter {
@Override
public Object write(Object value) {
if (!Date.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((Date) value);
} catch (Exception e) {
@@ -1256,14 +1256,19 @@ public class MappingElasticsearchConverter
PropertyValueConverter propertyValueConverter = Objects
.requireNonNull(persistentProperty.getPropertyValueConverter());
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) {
objects[i] = propertyValueConverter.write(objects[i]);
if (criteriaEntry.getKey().hasValue()) {
Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) {
objects[i] = propertyValueConverter.write(objects[i]);
}
} else {
criteriaEntry.setValue(propertyValueConverter.write(value));
}
} else {
criteriaEntry.setValue(propertyValueConverter.write(value));
}
});
}
@@ -61,6 +61,10 @@ public class TemporalPropertyValueConverter extends AbstractPropertyValueConvert
@Override
public Object write(Object value) {
if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) {
return value.toString();
}
try {
return dateConverters.get(0).format((TemporalAccessor) value);
} catch (Exception e) {
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,11 @@ package org.springframework.data.elasticsearch.core.document;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
@@ -38,13 +41,15 @@ import org.springframework.util.Assert;
/**
* This represents the complete search response from Elasticsearch, including the returned documents. Instances must be
* created with the {@link #from(SearchResponse,Function)} method.
* created with the {@link #from(SearchResponse, EntityCreator)} method.
*
* @author Peter-Josef Meisch
* @since 4.0
*/
public class SearchDocumentResponse {
private static final Log LOGGER = LogFactory.getLog(SearchDocumentResponse.class);
private final long totalHits;
private final String totalHitsRelation;
private final float maxScore;
@@ -98,12 +103,11 @@ public class SearchDocumentResponse {
* creates a SearchDocumentResponse from the {@link SearchResponse}
*
* @param searchResponse must not be {@literal null}
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the SearchDocumentResponse
*/
public static <T> SearchDocumentResponse from(SearchResponse searchResponse,
Function<SearchDocument, T> suggestEntityCreator) {
public static <T> SearchDocumentResponse from(SearchResponse searchResponse, EntityCreator<T> entityCreator) {
Assert.notNull(searchResponse, "searchResponse must not be null");
@@ -112,7 +116,7 @@ public class SearchDocumentResponse {
Aggregations aggregations = searchResponse.getAggregations();
org.elasticsearch.search.suggest.Suggest suggest = searchResponse.getSuggest();
return from(searchHits, scrollId, aggregations, suggest, suggestEntityCreator);
return from(searchHits, scrollId, aggregations, suggest, entityCreator);
}
/**
@@ -122,14 +126,14 @@ public class SearchDocumentResponse {
* @param scrollId scrollId
* @param aggregations aggregations
* @param suggestES the suggestion response from Elasticsearch
* @param suggestEntityCreator function to create an entity from a {@link SearchDocument}
* @param entityCreator function to create an entity from a {@link SearchDocument}
* @param <T> entity type
* @return the {@link SearchDocumentResponse}
* @since 4.3
*/
public static <T> SearchDocumentResponse from(SearchHits searchHits, @Nullable String scrollId,
@Nullable Aggregations aggregations, @Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> suggestEntityCreator) {
EntityCreator<T> entityCreator) {
TotalHits responseTotalHits = searchHits.getTotalHits();
@@ -153,14 +157,14 @@ public class SearchDocumentResponse {
}
}
Suggest suggest = suggestFrom(suggestES, suggestEntityCreator);
Suggest suggest = suggestFrom(suggestES, entityCreator);
return new SearchDocumentResponse(totalHits, totalHitsRelation, maxScore, scrollId, searchDocuments, aggregations,
suggest);
}
@Nullable
private static <T> Suggest suggestFrom(@Nullable org.elasticsearch.search.suggest.Suggest suggestES,
Function<SearchDocument, T> entityCreator) {
EntityCreator<T> entityCreator) {
if (suggestES == null) {
return null;
@@ -219,7 +223,19 @@ public class SearchDocumentResponse {
List<CompletionSuggestion.Entry.Option<T>> options = new ArrayList<>();
for (org.elasticsearch.search.suggest.completion.CompletionSuggestion.Entry.Option optionES : entryES) {
SearchDocument searchDocument = optionES.getHit() != null ? DocumentAdapters.from(optionES.getHit()) : null;
T hitEntity = searchDocument != null ? entityCreator.apply(searchDocument) : null;
T hitEntity = null;
if (searchDocument != null) {
try {
hitEntity = entityCreator.apply(searchDocument).get();
} catch (Exception e) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Error creating entity from SearchDocument");
}
}
}
options.add(new CompletionSuggestion.Entry.Option<T>(textToString(optionES.getText()),
textToString(optionES.getHighlighted()), optionES.getScore(), optionES.collateMatch(),
optionES.getContexts(), scoreDocFrom(optionES.getDoc()), searchDocument, hitEntity));
@@ -254,4 +270,14 @@ public class SearchDocumentResponse {
private static String textToString(@Nullable Text text) {
return text != null ? text.string() : "";
}
/**
* A function to convert a {@link SearchDocument} async into an entity. Asynchronous so that it can be used from the
* imperative and the reactive code.
*
* @param <T> the entity type
*/
@FunctionalInterface
public interface EntityCreator<T> extends Function<SearchDocument, CompletableFuture<T>> {}
}
@@ -20,7 +20,7 @@ import org.springframework.data.elasticsearch.core.convert.GeoConverters;
import org.springframework.data.elasticsearch.core.document.Document;
/**
* Interface definition for structures defined in <a href="https://geojson.org/>GeoJSON</a> format. copied from Spring
* Interface definition for structures defined in <a href="https://geojson.org/">GeoJSON</a> format. copied from Spring
* Data Mongodb
*
* @author Christoph Strobl
@@ -230,8 +230,7 @@ public class MappingBuilder {
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
String type = nestedOrObjectField ? fieldType.toString().toLowerCase()
: FieldType.Object.toString().toLowerCase();
String type = nestedOrObjectField ? fieldType.getMappedName() : FieldType.Object.getMappedName();
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
@@ -247,9 +246,9 @@ public class MappingBuilder {
}
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, entity.dynamic().getMappedName());
} else if (dynamicMapping != null) {
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES);
@@ -418,7 +417,7 @@ public class MappingBuilder {
ObjectNode contextNode = contextsNode.addObject();
contextNode.put(FIELD_CONTEXT_NAME, context.name());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase());
contextNode.put(FIELD_CONTEXT_TYPE, context.type().getMappedName());
if (context.precision().length() > 0) {
contextNode.put(FIELD_CONTEXT_PRECISION, context.precision());
@@ -450,7 +449,7 @@ public class MappingBuilder {
}
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
.put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) //
.put(FIELD_PARAM_TYPE, field.type().getMappedName()) //
.put(MAPPING_ENABLED, false) //
);
@@ -479,9 +478,9 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.dynamic() != Dynamic.INHERIT) {
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().getMappedName());
} else if (dynamicMapping != null) {
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
}
}
@@ -530,9 +529,9 @@ public class MappingBuilder {
if (nestedOrObjectField) {
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().getMappedName());
} else if (dynamicMapping != null) {
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().getMappedName());
}
}
@@ -237,9 +237,9 @@ public final class MappingParameters {
}
if (type != FieldType.Auto) {
objectNode.put(FIELD_PARAM_TYPE, type.toString().toLowerCase());
objectNode.put(FIELD_PARAM_TYPE, type.getMappedName());
if (type == FieldType.Date) {
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
List<String> formats = new ArrayList<>();
// built-in formats
@@ -24,7 +24,8 @@ package org.springframework.data.elasticsearch.core.mapping;
public interface PropertyValueConverter {
/**
* Converts a property value to an elasticsearch value.
* Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a
* String representation.
*
* @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value, must not be {@literal null}
@@ -172,7 +172,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
@Override
public boolean writeTypeHints() {
boolean writeTypeHints = contextConfiguration.writeTypeHints;
boolean writeTypeHints = contextConfiguration.getWriteTypeHints();
if (document != null) {
switch (document.writeTypeHint()) {
@@ -548,7 +548,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
/**
* Configuration settings passed in from the creating {@link SimpleElasticsearchMappingContext}.
*/
static class ContextConfiguration {
public static class ContextConfiguration {
private final FieldNamingStrategy fieldNamingStrategy;
private final boolean writeTypeHints;
@@ -561,6 +561,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
public FieldNamingStrategy getFieldNamingStrategy() {
return fieldNamingStrategy;
}
public boolean getWriteTypeHints() {
return writeTypeHints;
}
}
@Override
@@ -954,7 +954,23 @@ public class Criteria {
/**
* @since 4.3
*/
NOT_EMPTY
NOT_EMPTY;
/**
* @return true if this key does not have an associated value
* @since 4.4
*/
public boolean hasNoValue() {
return this == OperationKey.EXISTS || this == OperationKey.EMPTY || this == OperationKey.NOT_EMPTY;
}
/**
* @return true if this key does have an associated value
* @since 4.4
*/
public boolean hasValue() {
return !hasNoValue();
}
}
/**
@@ -967,9 +983,8 @@ public class Criteria {
protected CriteriaEntry(OperationKey key) {
boolean keyIsValid = key == OperationKey.EXISTS || key == OperationKey.EMPTY || key == OperationKey.NOT_EMPTY;
Assert.isTrue(keyIsValid,
"key must be OperationKey.EXISTS, OperationKey.EMPTY or OperationKey.EMPTY for this call");
Assert.isTrue(key.hasNoValue(),
"key must be OperationKey.EXISTS, OperationKey.EMPTY or OperationKey.NOT_EMPTY for this call");
this.key = key;
}
@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
* @author Mohsin Husen
* @author Peter-Josef Meisch
* @author Farid Faoudi
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html>docs</a>
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html">docs</a>
*/
public class UpdateQuery {
+4 -1
View File
@@ -1,4 +1,4 @@
Spring Data Elasticsearch 4.3 GA (2021.1.0)
Spring Data Elasticsearch 4.3.3 (2021.1.3)
Copyright (c) [2013-2021] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -28,6 +28,9 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
@@ -2783,6 +2783,33 @@ public abstract class ElasticsearchTemplateTests {
assertThat(searchHits.getSearchHit(1).getInnerHits("innerHits").getTotalHits()).isEqualTo(1);
}
@Test // #1997
@DisplayName("should return document with inner hits size zero")
void shouldReturnDocumentWithInnerHitsSizeZero() {
// given
SampleEntity sampleEntity = SampleEntity.builder().id(nextIdAsString()).message("message 1").rate(1)
.version(System.currentTimeMillis()).build();
List<IndexQuery> indexQueries = getIndexQueries(Arrays.asList(sampleEntity));
operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits").setSize(0)))
.build();
// when
SearchHits<SampleEntity> searchHits = operations.search(searchQuery, SampleEntity.class,
IndexCoordinates.of(indexNameProvider.indexName()));
// then
assertThat(searchHits).isNotNull();
assertThat(searchHits.getTotalHits()).isEqualTo(1);
assertThat(searchHits.getSearchHits()).hasSize(1);
assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("message 1");
}
private IndexQuery getIndexQuery(SampleEntity sampleEntity) {
return new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity)
.withVersion(sampleEntity.getVersion()).build();
@@ -66,6 +66,9 @@ import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
@@ -1496,6 +1499,16 @@ public class MappingElasticsearchConverterUnitTests {
assertThat(entity.getDontConvert()).isEqualTo("Monty Python's Flying Circus");
}
@Test // #2080
@DisplayName("should not try to call property converter on updating criteria exists")
void shouldNotTryToCallPropertyConverterOnUpdatingCriteriaExists() {
// don't care if the query makes no sense, we just add all criteria without values
Query query = new CriteriaQuery(Criteria.where("fieldWithClassBasedConverter").exists().empty().notEmpty());
mappingElasticsearchConverter.updateQuery(query, EntityWithCustomValueConverters.class);
}
private Map<String, Object> writeToMap(Object source) {
Document sink = Document.create();
@@ -2429,6 +2442,7 @@ public class MappingElasticsearchConverterUnitTests {
return reverse(value);
}
}
// endregion
private static String reverse(Object o) {
@@ -2436,5 +2450,4 @@ public class MappingElasticsearchConverterUnitTests {
return new StringBuilder().append(o.toString()).reverse().toString();
}
// endregion
}
@@ -0,0 +1,82 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core.convert;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity;
import org.springframework.lang.Nullable;
/**
* @author Peter-Josef Meisch
*/
public class PropertyValueConvertersUnitTests {
@ParameterizedTest(name = "{0}") // #2018
@MethodSource("propertyValueConverters")
@DisplayName("should return original object on write if it cannot be converted")
void shouldReturnOriginalObjectOnWriteIfItCannotBeConverted(PropertyValueConverter converter) {
NoConverterForThisClass value = new NoConverterForThisClass();
Object written = converter.write(value);
assertThat(written).isEqualTo(value.toString());
}
static Stream<Arguments> propertyValueConverters() {
SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext();
SimpleElasticsearchPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(NoConverterForThisClass.class);
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("property");
List<PropertyValueConverter> converters = new ArrayList<>();
converters.add(new DatePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new DateRangePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new NumberRangePropertyValueConverter(persistentProperty));
converters.add(new TemporalPropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
converters.add(new TemporalRangePropertyValueConverter(persistentProperty,
Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date))));
return converters.stream().map(propertyValueConverter -> arguments(
Named.of(propertyValueConverter.getClass().getSimpleName(), propertyValueConverter)));
}
static class NoConverterForThisClass {
@SuppressWarnings("unused")
@Nullable Long property;
}
}
@@ -38,10 +38,14 @@ import java.util.Objects;
import java.util.Set;
import org.assertj.core.data.Percentage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
@@ -57,6 +61,7 @@ import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Point;
@@ -79,10 +84,25 @@ import org.springframework.test.context.ContextConfiguration;
* @author Morgan Lutz
*/
@SpringIntegrationTest
@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class })
@ContextConfiguration(classes = { MappingBuilderIntegrationTests.Config.class })
public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("mapping-builder");
}
}
@Autowired private ElasticsearchOperations operations;
@Autowired IndexNameProvider indexNameProvider;
@BeforeEach
public void before() {
indexNameProvider.increment();
}
@Test
@Order(java.lang.Integer.MAX_VALUE)
@@ -132,8 +152,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Test // DATAES-76
public void shouldAddSampleInheritedEntityDocumentToIndex() {
// given
IndexCoordinates index = IndexCoordinates.of("test-index-sample-inherited-mapping-builder");
IndexOperations indexOps = operations.indexOps(index);
IndexOperations indexOps = operations.indexOps(SampleInheritedEntity.class);
// when
indexOps.create();
@@ -142,11 +161,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
String message = "msg";
String id = "abc";
operations.index(new SampleInheritedEntityBuilder(id).createdDate(createdDate).message(message).buildIndex(),
index);
operations.indexOps(SampleInheritedEntity.class).refresh();
IndexCoordinates.of(indexNameProvider.indexName()));
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
SearchHits<SampleInheritedEntity> result = operations.search(searchQuery, SampleInheritedEntity.class, index);
SearchHits<SampleInheritedEntity> result = operations.search(searchQuery, SampleInheritedEntity.class);
// then
assertThat(result).hasSize(1);
@@ -163,7 +181,7 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
IndexOperations indexOpsUser = operations.indexOps(User.class);
indexOpsUser.create();
indexOpsUser.putMapping(User.class);
indexNameProvider.increment();
IndexOperations indexOpsGroup = operations.indexOps(Group.class);
indexOpsGroup.create();
indexOpsGroup.putMapping(Group.class);
@@ -336,11 +354,19 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
@Test // #2024
@DisplayName("should map all field type values")
void shouldMapAllFieldTypeValues() {
operations.indexOps(EntityWithAllTypes.class).createWithMapping();
}
// region entities
@Document(indexName = "ignore-above-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class IgnoreAboveEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, ignoreAbove = 10) private String message;
@Nullable
public String getId() {
@@ -365,57 +391,76 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Document(indexName = "fieldname-index")
static class IdEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class TextEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Field(name = "text-property", type = FieldType.Text) //
@Nullable private String textProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class MappingEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Field("mapping-property") @Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") //
@Field("mapping-property")
@Mapping(mappingPath = "/mappings/test-field-analyzed-mappings.json") //
@Nullable private byte[] mappingProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class GeoPointEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field("geopoint-property") private GeoPoint geoPoint;
@Nullable
@Field("geopoint-property") private GeoPoint geoPoint;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CircularEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field(name = "circular-property", type = FieldType.Object, ignoreFields = { "circular-property" }) //
@Nullable
@Field(name = "circular-property", type = FieldType.Object, ignoreFields = { "circular-property" }) //
private CircularEntity circularProperty;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable @Field("completion-property") @CompletionField(maxInputLength = 100) //
@Nullable
@Field("completion-property")
@CompletionField(maxInputLength = 100) //
private Completion suggest;
}
@Document(indexName = "fieldname-index")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class MultiFieldEntity {
@Nullable @Id @Field("id-property") private String id;
@Nullable
@Id
@Field("id-property") private String id;
@Nullable //
@MultiField(mainField = @Field(name = "main-field", type = FieldType.Text, analyzer = "whitespace"),
@@ -425,13 +470,17 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-book-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Book {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String name;
@Nullable @Field(type = FieldType.Object) private Author author;
@Nullable @Field(type = FieldType.Nested) private Map<Integer, Collection<String>> buckets = new HashMap<>();
@Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"),
@Nullable
@Field(type = FieldType.Object) private Author author;
@Nullable
@Field(type = FieldType.Nested) private Map<Integer, Collection<String>> buckets = new HashMap<>();
@Nullable
@MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"),
otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop",
searchAnalyzer = "standard") }) private String description;
@@ -481,11 +530,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-simple-recursive-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SimpleRecursiveEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Object,
ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Object, ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject;
@Nullable
public String getId() {
@@ -506,12 +556,16 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-copy-to-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CopyToEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName;
@Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String lastName;
@Nullable @Field(type = FieldType.Keyword) private String name;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, copyTo = "name") private String firstName;
@Nullable
@Field(type = FieldType.Keyword, copyTo = "name") private String lastName;
@Nullable
@Field(type = FieldType.Keyword) private String name;
@Nullable
public String getId() {
@@ -550,12 +604,15 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-normalizer-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Setting(settingPath = "/settings/test-normalizer.json")
static class NormalizerEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name;
@Nullable @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case",
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name;
@Nullable
@MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case",
type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description;
@Nullable
@@ -610,10 +667,11 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-sample-inherited-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleInheritedEntity extends AbstractInheritedEntity {
@Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message;
@Nullable
@Field(type = Text, index = false, store = true, analyzer = "standard") private String message;
@Nullable
public String getMessage() {
@@ -656,11 +714,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-stock-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class StockPrice {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String symbol;
@Nullable @Field(type = FieldType.Double) private BigDecimal price;
@Nullable
@Field(type = FieldType.Double) private BigDecimal price;
@Nullable
public String getId() {
@@ -691,8 +751,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
static class AbstractInheritedEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate;
@Nullable
public String getId() {
@@ -713,21 +775,27 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-geo-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class GeoEntity {
@Nullable @Id private String id;
@Nullable
@Id private String id;
// geo shape - Spring Data
@Nullable private Box box;
@Nullable private Circle circle;
@Nullable private Polygon polygon;
// geo point - Custom implementation + Spring Data
@Nullable @GeoPointField private Point pointA;
@Nullable
@GeoPointField private Point pointA;
@Nullable private GeoPoint pointB;
@Nullable @GeoPointField private String pointC;
@Nullable @GeoPointField private double[] pointD;
@Nullable
@GeoPointField private String pointC;
@Nullable
@GeoPointField private double[] pointD;
// geo shape, until e have the classes for this, us a strng
@Nullable @GeoShapeField private String shape1;
@Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false,
@Nullable
@GeoShapeField private String shape1;
@Nullable
@GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false,
orientation = GeoShapeField.Orientation.clockwise) private String shape2;
@Nullable
@@ -821,17 +889,21 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-user-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class User {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set<Group> groups = new HashSet<>();
}
@Document(indexName = "test-index-group-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class Group {
@Nullable @Id String id;
@Nullable
@Id String id;
@Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set<User> users = new HashSet<>();
}
@@ -848,11 +920,14 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "completion")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionDocument {
@Nullable @Id private String id;
@Nullable @CompletionField(contexts = { @CompletionContext(name = "location",
type = CompletionContext.ContextMappingType.GEO, path = "proppath") }) private Completion suggest;
@Nullable
@Id private String id;
@Nullable
@CompletionField(contexts = { @CompletionContext(name = "location", type = CompletionContext.ContextMappingType.GEO,
path = "proppath") }) private Completion suggest;
@Nullable
public String getId() {
@@ -873,9 +948,10 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-entity-with-seq-no-primary-term-mapping-builder")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class EntityWithSeqNoPrimaryTerm {
@Nullable @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm;
@Nullable
@Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm;
@Nullable
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
@@ -888,10 +964,14 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
static class RankFeatureEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Rank_Feature) private Integer pageRank;
@Nullable @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength;
@Nullable @Field(type = FieldType.Rank_Features) private Map<String, Integer> topics;
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Rank_Feature) private Integer pageRank;
@Nullable
@Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength;
@Nullable
@Field(type = FieldType.Rank_Features) private Map<String, Integer> topics;
@Nullable
public String getId() {
@@ -930,18 +1010,25 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "termvectors-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class TermVectorFieldEntity {
@Nullable @Id private String id;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.no) private String no;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.yes) private String yes;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions;
@Nullable @Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets;
@Nullable @Field(type = FieldType.Text,
termVector = TermVector.with_positions_offsets) private String with_positions_offsets;
@Nullable @Field(type = FieldType.Text,
@Nullable
@Id private String id;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.no) private String no;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.yes) private String yes;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets;
@Nullable
@Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets;
@Nullable
@Field(type = FieldType.Text,
termVector = TermVector.with_positions_payloads) private String with_positions_payloads;
@Nullable @Field(type = FieldType.Text,
@Nullable
@Field(type = FieldType.Text,
termVector = TermVector.with_positions_offsets_payloads) private String with_positions_offsets_payloads;
@Nullable
@@ -1017,10 +1104,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "wildcard-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class WildcardEntity {
@Nullable @Field(type = Wildcard) private String wildcardWithoutParams;
@Nullable @Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams;
@Nullable
@Field(type = Wildcard) private String wildcardWithoutParams;
@Nullable
@Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams;
@Nullable
public String getWildcardWithoutParams() {
@@ -1041,11 +1130,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "disabled-entity-mapping")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(enabled = false)
static class DisabledMappingEntity {
@Nullable @Id private String id;
@Nullable @Field(type = Text) private String text;
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String text;
@Nullable
public String getId() {
@@ -1068,9 +1159,13 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@Document(indexName = "disabled-property-mapping")
static class DisabledMappingProperty {
@Nullable @Id private String id;
@Nullable @Field(type = Text) private String text;
@Nullable @Mapping(enabled = false) @Field(type = Object) private Object object;
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String text;
@Nullable
@Mapping(enabled = false)
@Field(type = Object) private Object object;
@Nullable
public String getId() {
@@ -1100,10 +1195,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "densevector-test")
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class DenseVectorEntity {
@Nullable @Id private String id;
@Nullable @Field(type = Dense_Vector, dims = 3) private float[] dense_vector;
@Nullable
@Id private String id;
@Nullable
@Field(type = Dense_Vector, dims = 3) private float[] dense_vector;
@Nullable
public String getId() {
@@ -1124,15 +1221,19 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping-annotation")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@DynamicMapping(DynamicMappingValue.False)
static class DynamicMappingAnnotationEntity {
@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
@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() {
@@ -1144,57 +1245,140 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE)
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class DynamicMappingEntity {
@Nullable @Field(type = FieldType.Object) //
@Nullable
@Field(type = FieldType.Object) //
private Map<String, Object> objectInherit;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
private Map<String, Object> objectFalse;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
private Map<String, Object> objectTrue;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
private Map<String, Object> objectStrict;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
@Nullable
@Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
private Map<String, Object> objectRuntime;
@Nullable @Field(type = FieldType.Nested) //
@Nullable
@Field(type = FieldType.Nested) //
private List<Map<String, Object>> nestedObjectInherit;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
private List<Map<String, Object>> nestedObjectFalse;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
private List<Map<String, Object>> nestedObjectTrue;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
private List<Map<String, Object>> nestedObjectStrict;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
@Nullable
@Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
private List<Map<String, Object>> nestedObjectRuntime;
}
@Document(indexName = "dynamic-detection-mapping-true")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
dynamicDateFormats = { "MM/dd/yyyy" })
private static class DynamicDetectionMapping {
@Id @Nullable private String id;
@Id
@Nullable private String id;
}
@Document(indexName = "runtime-fields")
@Document(indexName = "#{@indexNameProvider.indexName()}")
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
private static class RuntimeFieldEntity {
@Id @Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
@Id
@Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp")
@Nullable private Instant timestamp;
}
@Document(indexName = "fields-excluded-from-source")
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class ExcludedFieldEntity {
@Id @Nullable private String id;
@Nullable @Field(name = "excluded-date", type = Date, format = DateFormat.date,
@Id
@Nullable private String id;
@Nullable
@Field(name = "excluded-date", type = Date, format = DateFormat.date,
excludeFromSource = true) private LocalDate excludedDate;
@Nullable @Field(type = Nested) private NestedExcludedFieldEntity nestedEntity;
@Nullable
@Field(type = Nested) private NestedExcludedFieldEntity nestedEntity;
}
private static class NestedExcludedFieldEntity {
@Nullable @Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText;
@Nullable
@Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText;
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
private static class EntityWithAllTypes {
@Nullable
@Field(type = FieldType.Auto) String autoField;
@Nullable
@Field(type = FieldType.Text) String textField;
@Nullable
@Field(type = FieldType.Keyword) String keywordField;
@Nullable
@Field(type = FieldType.Long) String longField;
@Nullable
@Field(type = FieldType.Integer) String integerField;
@Nullable
@Field(type = FieldType.Short) String shortField;
@Nullable
@Field(type = FieldType.Byte) String byteField;
@Nullable
@Field(type = FieldType.Double) String doubleField;
@Nullable
@Field(type = FieldType.Float) String floatField;
@Nullable
@Field(type = FieldType.Half_Float) String halfFloatField;
@Nullable
@Field(type = FieldType.Scaled_Float) String scaledFloatField;
@Nullable
@Field(type = FieldType.Date) String dateField;
@Nullable
@Field(type = FieldType.Date_Nanos) String dateNanosField;
@Nullable
@Field(type = FieldType.Boolean) String booleanField;
@Nullable
@Field(type = FieldType.Binary) String binaryField;
@Nullable
@Field(type = FieldType.Integer_Range) String integerRangeField;
@Nullable
@Field(type = FieldType.Float_Range) String floatRangeField;
@Nullable
@Field(type = FieldType.Long_Range) String longRangeField;
@Nullable
@Field(type = FieldType.Double_Range) String doubleRangeField;
@Nullable
@Field(type = FieldType.Date_Range) String dateRangeField;
@Nullable
@Field(type = FieldType.Ip_Range) String ipRangeField;
@Nullable
@Field(type = FieldType.Object) String objectField;
@Nullable
@Field(type = FieldType.Nested) String nestedField;
@Nullable
@Field(type = FieldType.Ip) String ipField;
@Nullable
@Field(type = FieldType.TokenCount, analyzer = "standard") String tokenCountField;
@Nullable
@Field(type = FieldType.Percolator) String percolatorField;
@Nullable
@Field(type = FieldType.Flattened) String flattenedField;
@Nullable
@Field(type = FieldType.Search_As_You_Type) String searchAsYouTypeField;
@Nullable
@Field(type = FieldType.Rank_Feature) String rankFeatureField;
@Nullable
@Field(type = FieldType.Rank_Features) String rankFeaturesField;
@Nullable
@Field(type = FieldType.Wildcard) String wildcardField;
@Nullable
@Field(type = FieldType.Dense_Vector, dims = 1) String denseVectorField;
}
// endregion
}
@@ -42,6 +42,7 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.core.MappingContextBaseTests;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
@@ -721,6 +722,29 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, false);
}
@Test // #2102
@DisplayName("should write date formats for date range fields")
void shouldWriteDateFormatsForDateRangeFields() throws JSONException {
String expected = "{\n" + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" },\n" + //
" \"field2\": {\n" + //
" \"type\": \"date_range\",\n" + //
" \"format\": \"date\"\n" + //
" }\n" + //
" }\n" + //
"}\n"; //
String mapping = getMappingBuilder().buildPropertyMapping(DateRangeEntity.class);
assertEquals(expected, mapping, false);
}
@Test // #1454
@DisplayName("should write type hints when context is configured to do so")
void shouldWriteTypeHintsWhenContextIsConfiguredToDoSo() throws JSONException {
@@ -1911,6 +1935,31 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
}
}
private static class DateRangeEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Date_Range, format = DateFormat.date) private Range<LocalDateTime> field2;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public Range<LocalDateTime> getField2() {
return field2;
}
public void setField2(@Nullable Range<LocalDateTime> field2) {
this.field2 = field2;
}
}
@Document(indexName = "magazine")
private static class Magazine {
@Id @Nullable private String id;
@@ -265,23 +265,30 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
// region entities
static class FieldNameProperty {
@Nullable @Field(name = "by-name") String fieldProperty;
@Nullable
@Field(name = "by-name") String fieldProperty;
}
static class FieldValueProperty {
@Nullable @Field(value = "by-value") String fieldProperty;
@Nullable
@Field(value = "by-value") String fieldProperty;
}
static class MultiFieldProperty {
@Nullable @MultiField(mainField = @Field("mainfield"),
@Nullable
@MultiField(mainField = @Field("mainfield"),
otherFields = { @InnerField(suffix = "suff", type = FieldType.Keyword) }) String mainfieldProperty;
}
static class DatesProperty {
@Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate;
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
@Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
@Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List<LocalDate> localDateList;
@Nullable
@Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime;
@Nullable
@Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate;
@Nullable
@Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List<LocalDate> localDateList;
}
static class SeqNoPrimaryTermProperty {
@@ -344,8 +351,10 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
private static class EntityWithCustomValueConverters {
@Id private String id;
@Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
@Nullable
@ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable
@ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
}
private static class ClassBasedValueConverter implements PropertyValueConverter {
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2021-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.springframework.data.elasticsearch.core.suggest;
import static org.assertj.core.api.Assertions.*;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
@@ -39,8 +40,8 @@ import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.suggest.response.CompletionSuggestion;
import org.springframework.data.elasticsearch.core.suggest.response.Suggest;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
@@ -86,13 +87,11 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@DisplayName("should find suggestions for given prefix completion")
void shouldFindSuggestionsForGivenPrefixCompletion() {
loadCompletionObjectEntities();
NativeSearchQuery query = new NativeSearchQueryBuilder().withSuggestBuilder(new SuggestBuilder()
.addSuggestion("test-suggest", SuggestBuilders.completionSuggestion("suggest").prefix("m", Fuzziness.AUTO)))
.build();
operations.suggest(query, CompletionEntity.class) //
loadCompletionObjectEntities() //
.flatMap(unused -> {
Query query = getSuggestQuery("test-suggest", "suggest", "m");
return operations.suggest(query, CompletionEntity.class);
}) //
.as(StepVerifier::create) //
.assertNext(suggest -> {
Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest
@@ -105,13 +104,21 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
assertThat(options).hasSize(2);
assertThat(options.get(0).getText()).isIn("Marchand", "Mohsin");
assertThat(options.get(1).getText()).isIn("Marchand", "Mohsin");
}) //
.verifyComplete();
}
protected Query getSuggestQuery(String suggestionName, String fieldName, String prefix) {
return new NativeSearchQueryBuilder() //
.withSuggestBuilder(new SuggestBuilder() //
.addSuggestion(suggestionName, //
SuggestBuilders.completionSuggestion(fieldName) //
.prefix(prefix, Fuzziness.AUTO))) //
.build(); //
}
// region helper functions
private void loadCompletionObjectEntities() {
private Mono<CompletionEntity> loadCompletionObjectEntities() {
CompletionEntity rizwan_idrees = new CompletionEntityBuilder("1").name("Rizwan Idrees")
.suggest(new String[] { "Rizwan Idrees" }).build();
@@ -124,7 +131,7 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
List<CompletionEntity> entities = new ArrayList<>(
Arrays.asList(rizwan_idrees, franck_marchand, mohsin_husen, artur_konczak));
IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName());
operations.saveAll(entities, index).blockLast();
return operations.saveAll(entities, index).last();
}
// endregion
@@ -132,11 +139,13 @@ public class ReactiveElasticsearchTemplateSuggestIntegrationTests {
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class CompletionEntity {
@Nullable @Id private String id;
@Nullable
@Id private String id;
@Nullable private String name;
@Nullable @CompletionField(maxInputLength = 100) private Completion suggest;
@Nullable
@CompletionField(maxInputLength = 100) private Completion suggest;
private CompletionEntity() {}