Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97939f58dc | |||
| 8bccf42d9c | |||
| ccb5016928 | |||
| 3757d9d1d6 | |||
| 4796629f6e | |||
| dde239cab6 | |||
| f34dbb1915 | |||
| 8875fb79a8 | |||
| f6af6b6f33 | |||
| 6b3646bcc1 | |||
| 1cccc1cbb2 | |||
| 2c8e18ac35 | |||
| c11146b0f2 | |||
| 01be7415db | |||
| 77c596ec01 | |||
| 7f68f50e56 | |||
| 89b3199c3f | |||
| ad77924370 | |||
| ce54645bbe | |||
| 93363c9e04 | |||
| da40eb1b11 | |||
| 911d80e77e | |||
| bd8f947a47 | |||
| 9af8e2970a | |||
| 3a295cfa12 | |||
| efe7932d42 | |||
| 26a7b48c4b | |||
| 34a0d9b600 | |||
| cb32d1991d | |||
| fbf9c355fa | |||
| 2dbca48cdf | |||
| e7366973b7 | |||
| 53ac53d146 | |||
| 6c656dff17 | |||
| cfa303c6a3 | |||
| 353c463aa8 | |||
| cb67bfb534 | |||
| 49cce254ce | |||
| 597409c4c2 | |||
| 66144d10f8 | |||
| e6aefc8180 | |||
| a7bd311106 | |||
| 003d75f022 | |||
| 535b407085 | |||
| 44919d4cbe | |||
| 6260f278ba | |||
| b3bd77aa46 | |||
| bea651bc95 | |||
| 175614cd94 | |||
| c4c73709c8 |
@@ -3,7 +3,7 @@ name: CI Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, 'issue/**' ]
|
||||
branches: [ 6.0.x, 'issue/6.0.x/**' ]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
@@ -17,11 +17,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Java and Maven
|
||||
uses: spring-projects/spring-data-build/actions/setup-maven@main
|
||||
uses: spring-projects/spring-data-build/actions/setup-maven@4.0.x
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
develocity-access-key: '${{ secrets.DEVELOCITY_ACCESS_KEY }}'
|
||||
- name: Build
|
||||
uses: spring-projects/spring-data-build/actions/maven-build@main
|
||||
uses: spring-projects/spring-data-build/actions/maven-build@4.0.x
|
||||
env:
|
||||
TESTCONTAINERS_REUSE_ENABLE: true
|
||||
with:
|
||||
settings-xml: '${{ vars.SETTINGS_XML }}'
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# GitHub Actions to automate GitHub issues for Spring Data Project Management
|
||||
|
||||
name: Spring Data GitHub Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened, edited, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
Inbox:
|
||||
runs-on: ubuntu-latest
|
||||
if: vars.PROJECT_CARDS_ENABLED == 'true' && github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request == null && !contains(join(github.event.issue.labels.*.name, ', '), 'dependency-upgrade') && !contains(github.event.issue.title, 'Release ')
|
||||
steps:
|
||||
- name: Create or Update Issue Card
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/spring-projects/projects/25
|
||||
github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
Pull-Request:
|
||||
runs-on: ubuntu-latest
|
||||
if: vars.PROJECT_CARDS_ENABLED == 'true' && github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request != null
|
||||
steps:
|
||||
- name: Create or Update Pull Request Card
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/spring-projects/projects/25
|
||||
github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
Feedback-Provided:
|
||||
runs-on: ubuntu-latest
|
||||
if: vars.PROJECT_CARDS_ENABLED == 'true' && github.repository_owner == 'spring-projects' && github.event_name == 'issue_comment' && github.event.action == 'created' && github.actor != 'spring-projects-issues' && github.event.pull_request == null && github.event.issue.state == 'open' && contains(toJSON(github.event.issue.labels), 'waiting-for-feedback')
|
||||
steps:
|
||||
- name: Update Project Card
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: https://github.com/orgs/spring-projects/projects/25
|
||||
github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
|
||||
@@ -3,7 +3,7 @@ name: Snapshots
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ main, 'issue/**' ]
|
||||
branches: [ 6.0.x, 'issue/6.0.x/**' ]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
@@ -15,14 +15,18 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Java and Maven
|
||||
uses: spring-projects/spring-data-build/actions/setup-maven@main
|
||||
uses: spring-projects/spring-data-build/actions/setup-maven@4.0.x
|
||||
with:
|
||||
develocity-access-key: '${{ secrets.DEVELOCITY_ACCESS_KEY }}'
|
||||
- name: Deploy to Artifactory
|
||||
uses: spring-projects/spring-data-build/actions/maven-artifactory-deploy@main
|
||||
uses: spring-projects/spring-data-build/actions/maven-artifactory-deploy@4.0.x
|
||||
env:
|
||||
TESTCONTAINERS_REUSE_ENABLE: true
|
||||
with:
|
||||
build-name: 'spring-data-elasticsearch'
|
||||
username: '${{ secrets.ARTIFACTORY_USERNAME }}'
|
||||
password: '${{ secrets.ARTIFACTORY_PASSWORD }}'
|
||||
context-url: '${{ vars.ARTIFACTORY_CONTEXT_URL }}'
|
||||
repository: '${{ vars.ARTIFACTORY_REPOSITORY }}'
|
||||
project: '${{ vars.ARTIFACTORY_PROJECT }}'
|
||||
settings-xml: '${{ vars.SETTINGS_XML }}'
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
#Thu Jul 17 13:59:56 CEST 2025
|
||||
#Tue Jun 02 14:59:45 CEST 2026
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
|
||||
wrapperUrl=https\://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-elasticsearch</artifactId>
|
||||
<version>6.1.0-RC1</version>
|
||||
<version>6.0.6</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>4.1.0-RC1</version>
|
||||
<version>4.0.6</version>
|
||||
</parent>
|
||||
|
||||
<name>Spring Data Elasticsearch</name>
|
||||
@@ -18,14 +18,14 @@
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
|
||||
<properties>
|
||||
<springdata.commons>4.1.0-RC1</springdata.commons>
|
||||
<springdata.commons>4.0.6</springdata.commons>
|
||||
|
||||
<!-- version of the ElasticsearchClient -->
|
||||
<elasticsearch-java>9.3.4</elasticsearch-java>
|
||||
<elasticsearch-rest-client>9.3.3</elasticsearch-rest-client>
|
||||
<elasticsearch-java>9.2.8</elasticsearch-java>
|
||||
<elasticsearch-rest-client>9.2.8</elasticsearch-rest-client>
|
||||
|
||||
<hoverfly>0.20.2</hoverfly>
|
||||
<log4j>2.25.4</log4j>
|
||||
<log4j>2.25.1</log4j>
|
||||
<jsonassert>1.5.3</jsonassert>
|
||||
<wiremock>3.9.2</wiremock>
|
||||
|
||||
@@ -82,8 +82,7 @@
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-data-elasticsearch</url>
|
||||
<connection>scm:git:git://github.com/spring-projects/spring-data-elasticsearch.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-data-elasticsearch.git
|
||||
</developerConnection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-data-elasticsearch.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
@@ -179,30 +178,17 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson JSON Mapper -->
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Version 2 to use with the legacy RestClient -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- CDI -->
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
<artifactId>javax.interceptor-api</artifactId>
|
||||
@@ -355,13 +341,7 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-observation-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<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>
|
||||
@@ -1,20 +1,12 @@
|
||||
[[new-features]]
|
||||
= What's new
|
||||
|
||||
[[new-features.6-1-0]]
|
||||
== New in Spring Data Elasticsearch 6.1
|
||||
|
||||
* Upgrade to Elasticsearch 9.3.3/ Client 9.3.4
|
||||
* Add support to use `IndexCoordinates` as repository query parameter
|
||||
* Add support for includeNamedQueriesScore in Query
|
||||
* Add support for Micrometer observation.
|
||||
|
||||
[[new-features.6-0-0]]
|
||||
== New in Spring Data Elasticsearch 6.0
|
||||
|
||||
* Upgrade to Spring 7
|
||||
* Switch to jspecify nullability annotations
|
||||
* Upgrade to Elasticsearch 9.2.1
|
||||
* Upgrade to Elasticsearch 9.2.2
|
||||
* Use the new Elasticsearch Rest5Client as default
|
||||
* Add support for SpEL expressions in the `settingPath` parameter of the `@Setting` annotation
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ The following table shows the Elasticsearch and Spring versions that are used by
|
||||
[cols="^,^,^,^",options="header"]
|
||||
|===
|
||||
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework
|
||||
| 2026.0 | 6.1.x | 9.3.4 | 7.0.x
|
||||
| 2025.1 | 6.0.x | 9.2.2 | 7.0.x
|
||||
| 2025.1 | 6.0.x | 9.2.8 | 7.0.x
|
||||
| 2025.0 | 5.5.x | 8.18.1 | 6.2.x
|
||||
| 2024.1 | 5.4.xfootnote:oom[Out of maintenance] | 8.15.5 | 6.1.x
|
||||
| 2024.0 | 5.3.xfootnote:oom[] | 8.13.4 | 6.1.x
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
[[elasticsearch.projections]]
|
||||
= Projections
|
||||
|
||||
[[elasticsearch.projections.limitations]]
|
||||
== Spring Data Elasticsearch Projection Limitations
|
||||
|
||||
This chapter is pulled in from the Spring Data Commons documentation, but does not apply to Spring Data Elasticsearch.
|
||||
|
||||
IMPORTANT: Interface-based projections are not supported in Spring Data Elasticsearch repository query methods.
|
||||
|
||||
To limit the fields returned from Elasticsearch, use the xref:elasticsearch/repositories/elasticsearch-repositories.adoc#elasticsearch.repositories.annotations.sourcefilters[`@SourceFilters`] annotation on your repository methods instead.
|
||||
|
||||
include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1]
|
||||
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
/**
|
||||
* Default {@link ElasticsearchObservationConvention} implementation.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
public class DefaultElasticsearchObservationConvention implements ElasticsearchObservationConvention {
|
||||
|
||||
public static final DefaultElasticsearchObservationConvention INSTANCE = new DefaultElasticsearchObservationConvention();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ElasticsearchObservationContext context) {
|
||||
|
||||
String indexName = context.getIndexName();
|
||||
if (indexName != null) {
|
||||
return context.getOperationName().getValue() + " " + indexName;
|
||||
}
|
||||
return context.getOperationName().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ElasticsearchObservationContext context) {
|
||||
|
||||
KeyValues keyValues = KeyValues.of(
|
||||
ElasticsearchObservation.LowCardinalityKeyNames.OPERATION.withValue(context.getOperationName().getValue()));
|
||||
|
||||
String indexName = context.getIndexName();
|
||||
if (indexName != null) {
|
||||
keyValues = keyValues.and(ElasticsearchObservation.LowCardinalityKeyNames.COLLECTION.withValue(indexName));
|
||||
}
|
||||
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ElasticsearchObservationContext context) {
|
||||
|
||||
Integer batchSize = context.getBatchSize();
|
||||
if (batchSize != null) {
|
||||
return KeyValues.of(
|
||||
ElasticsearchObservation.HighCardinalityKeyNames.BATCH_SIZE.withValue(String.valueOf(batchSize)));
|
||||
}
|
||||
|
||||
return KeyValues.empty();
|
||||
}
|
||||
}
|
||||
+9
-8
@@ -17,14 +17,12 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.Jackson3JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import tools.jackson.databind.cfg.JsonNodeFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.elasticsearch.client.ClientConfiguration;
|
||||
@@ -34,6 +32,10 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving
|
||||
@@ -126,11 +128,10 @@ public abstract class ElasticsearchConfiguration extends ElasticsearchConfigurat
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
JsonMapper jsonMapper = JsonMapper.builder()
|
||||
.enable(JsonNodeFeature.WRITE_NULL_PROPERTIES)
|
||||
.enable(JsonNodeFeature.READ_NULL_PROPERTIES)
|
||||
.build();
|
||||
return new Jackson3JsonpMapper(jsonMapper);
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
* @since 4.4
|
||||
* @deprecated since 6.0, use {@link ElasticsearchConfiguration}
|
||||
*/
|
||||
@Deprecated(since = "6.0", forRemoval = true)
|
||||
@Deprecated(since = "6.0", forRemoval=true)
|
||||
public abstract class ElasticsearchLegacyRestClientConfiguration extends ElasticsearchConfigurationSupport {
|
||||
|
||||
/**
|
||||
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import io.micrometer.common.docs.KeyName;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
import io.micrometer.observation.docs.ObservationDocumentation;
|
||||
|
||||
/**
|
||||
* {@link ObservationDocumentation} for Spring Data Elasticsearch template operations.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
public enum ElasticsearchObservation implements ObservationDocumentation {
|
||||
|
||||
/**
|
||||
* Timer created around a Spring Data Elasticsearch template operation.
|
||||
*/
|
||||
ELASTICSEARCH_COMMAND_OBSERVATION {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "spring.data.elasticsearch.command";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
|
||||
return DefaultElasticsearchObservationConvention.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyName[] getLowCardinalityKeyNames() {
|
||||
return LowCardinalityKeyNames.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyName[] getHighCardinalityKeyNames() {
|
||||
return HighCardinalityKeyNames.values();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Low cardinality key names for Spring Data Elasticsearch observations. These become metric dimensions and MUST be
|
||||
* present on every observation to satisfy backends like Prometheus that require consistent tag key sets.
|
||||
*/
|
||||
enum LowCardinalityKeyNames implements KeyName {
|
||||
|
||||
/**
|
||||
* The Spring Data operation being performed (e.g., save, search, delete, bulk).
|
||||
*/
|
||||
OPERATION {
|
||||
@Override
|
||||
public String asString() {
|
||||
return "spring.data.operation";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The target collection (index) name. Only present when the operation targets a specific index.
|
||||
*/
|
||||
COLLECTION {
|
||||
@Override
|
||||
public String asString() {
|
||||
return "spring.data.collection";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High cardinality key names for Spring Data Elasticsearch observations. These appear only on traces/spans, not on
|
||||
* metrics, because their values are unbounded or optional per operation.
|
||||
*/
|
||||
enum HighCardinalityKeyNames implements KeyName {
|
||||
|
||||
/**
|
||||
* The number of operations included in a batch (bulk) request. Only present for bulk operations.
|
||||
*/
|
||||
BATCH_SIZE {
|
||||
@Override
|
||||
public String asString() {
|
||||
return "spring.data.batch.size";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
|
||||
/**
|
||||
* {@link Observation.Context} for Spring Data Elasticsearch operations. One instance is created per observed operation.
|
||||
* It carries contextual data that conventions use to produce observation names and key-values.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
public class ElasticsearchObservationContext extends Observation.Context {
|
||||
|
||||
private final ElasticsearchOperationName operationName;
|
||||
@Nullable private final IndexCoordinates indexCoordinates;
|
||||
@Nullable private Integer batchSize;
|
||||
|
||||
public ElasticsearchObservationContext(ElasticsearchOperationName operationName,
|
||||
@Nullable IndexCoordinates indexCoordinates) {
|
||||
this.operationName = operationName;
|
||||
this.indexCoordinates = indexCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Spring Data operation being performed.
|
||||
*/
|
||||
public ElasticsearchOperationName getOperationName() {
|
||||
return operationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the target index coordinates, or {@literal null} if the operation is not index-specific.
|
||||
*/
|
||||
@Nullable
|
||||
public IndexCoordinates getIndexCoordinates() {
|
||||
return indexCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the comma-joined index name(s), or {@literal null} if no index coordinates are set.
|
||||
*/
|
||||
@Nullable
|
||||
public String getIndexName() {
|
||||
return indexCoordinates != null ? String.join(",", indexCoordinates.getIndexNames()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the batch size, or {@literal null} if not a batch operation.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of operations included in a batch (bulk) request.
|
||||
*
|
||||
* @param batchSize the batch size, can be {@literal null}
|
||||
*/
|
||||
public void setBatchSize(@Nullable Integer batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
/**
|
||||
* {@link ObservationConvention} for Spring Data Elasticsearch operations. Implement this interface and register it as a
|
||||
* bean to customize observation names and key-values.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
public interface ElasticsearchObservationConvention extends ObservationConvention<ElasticsearchObservationContext> {
|
||||
|
||||
@Override
|
||||
default boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof ElasticsearchObservationContext;
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
/**
|
||||
* Enumeration of Spring Data Elasticsearch operation names used in observations.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
public enum ElasticsearchOperationName {
|
||||
|
||||
SAVE("save"), //
|
||||
INDEX("index"), //
|
||||
GET("get"), //
|
||||
MULTI_GET("multiGet"), //
|
||||
EXISTS("exists"), //
|
||||
DELETE("delete"), //
|
||||
DELETE_BY_QUERY("deleteByQuery"), //
|
||||
BULK("bulk"), //
|
||||
UPDATE("update"), //
|
||||
UPDATE_BY_QUERY("updateByQuery"), //
|
||||
COUNT("count"), //
|
||||
SEARCH("search");
|
||||
|
||||
private final String value;
|
||||
|
||||
ElasticsearchOperationName(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the operation name as a string value used in observation key values.
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+55
-159
@@ -17,11 +17,30 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.Time;
|
||||
import co.elastic.clients.elasticsearch.core.*;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultiSearchResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.elasticsearch.sql.ElasticsearchSqlClient;
|
||||
import co.elastic.clients.elasticsearch.sql.QueryResponse;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.client.UnsupportedBackendOperation;
|
||||
import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate;
|
||||
@@ -43,30 +62,6 @@ import org.springframework.data.elasticsearch.core.script.Script;
|
||||
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.Time;
|
||||
import co.elastic.clients.elasticsearch.core.*;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.msearch.MultiSearchResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.elasticsearch.sql.ElasticsearchSqlClient;
|
||||
import co.elastic.clients.elasticsearch.sql.QueryResponse;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.data.elasticsearch.core.ElasticsearchOperations} using the new
|
||||
* Elasticsearch client.
|
||||
@@ -75,15 +70,12 @@ import io.micrometer.observation.ObservationRegistry;
|
||||
* @author Hamid Rahimi
|
||||
* @author Illia Ulianov
|
||||
* @author Haibo Liu
|
||||
* @author maryantocinn
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ElasticsearchTemplate.class);
|
||||
|
||||
@Nullable private ElasticsearchObservationConvention observationConvention;
|
||||
|
||||
private final ElasticsearchClient client;
|
||||
private final ElasticsearchSqlClient sqlClient;
|
||||
private final RequestConverter requestConverter;
|
||||
@@ -121,61 +113,6 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
protected AbstractElasticsearchTemplate doCopy() {
|
||||
return new ElasticsearchTemplate(client, elasticsearchConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeCopy(AbstractElasticsearchTemplate copy) {
|
||||
|
||||
if (copy instanceof ElasticsearchTemplate elasticsearchTemplate) {
|
||||
elasticsearchTemplate.observationConvention = this.observationConvention;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
super.setApplicationContext(applicationContext);
|
||||
|
||||
if (observationRegistry == ObservationRegistry.NOOP) {
|
||||
applicationContext.getBeanProvider(ObservationRegistry.class).ifAvailable(this::setObservationRegistry);
|
||||
}
|
||||
|
||||
if (observationConvention == null) {
|
||||
applicationContext.getBeanProvider(ElasticsearchObservationConvention.class)
|
||||
.ifAvailable(this::setObservationConvention);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@link ElasticsearchObservationConvention} to override the default convention.
|
||||
*
|
||||
* @param observationConvention can be {@literal null}.
|
||||
* @since 6.1
|
||||
*/
|
||||
public void setObservationConvention(@Nullable ElasticsearchObservationConvention observationConvention) {
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
private <T> T observe(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
Supplier<T> action) {
|
||||
Observation observation = createObservation(operationName, index, null);
|
||||
return observation.observe(action);
|
||||
}
|
||||
|
||||
private <T> T observe(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index, int batchSize,
|
||||
Supplier<T> action) {
|
||||
Observation observation = createObservation(operationName, index, batchSize);
|
||||
return observation.observe(action);
|
||||
}
|
||||
|
||||
private Observation createObservation(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
@Nullable Integer batchSize) {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(operationName, index);
|
||||
context.setBatchSize(batchSize);
|
||||
|
||||
return ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(observationConvention,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, observationRegistry);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region child templates
|
||||
@@ -200,45 +137,16 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
// endregion
|
||||
|
||||
// region document operations
|
||||
@Override
|
||||
public <T> T save(T entity, IndexCoordinates index) {
|
||||
return observe(ElasticsearchOperationName.SAVE, index, () -> super.save(entity, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String index(IndexQuery query, IndexCoordinates index) {
|
||||
return observe(ElasticsearchOperationName.INDEX, index, () -> super.index(query, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String id, IndexCoordinates index) {
|
||||
return observe(ElasticsearchOperationName.EXISTS, index, () -> super.exists(id, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delete(String id, IndexCoordinates index) {
|
||||
return observe(ElasticsearchOperationName.DELETE, index, () -> super.delete(id, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndexedObjectInformation> bulkOperation(List<?> queries, BulkOptions bulkOptions,
|
||||
IndexCoordinates index) {
|
||||
return observe(ElasticsearchOperationName.BULK, index, queries.size(),
|
||||
() -> super.bulkOperation(queries, bulkOptions, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
return observe(ElasticsearchOperationName.GET, index, () -> {
|
||||
GetRequest getRequest = requestConverter.documentGetRequest(elasticsearchConverter.convertId(id),
|
||||
routingResolver.getRouting(), index);
|
||||
GetResponse<EntityAsMap> getResponse = execute(client -> client.get(getRequest, EntityAsMap.class));
|
||||
GetRequest getRequest = requestConverter.documentGetRequest(elasticsearchConverter.convertId(id),
|
||||
routingResolver.getRouting(), index);
|
||||
GetResponse<EntityAsMap> getResponse = execute(client -> client.get(getRequest, EntityAsMap.class));
|
||||
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
return callback.doWith(DocumentAdapters.from(getResponse));
|
||||
});
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
return callback.doWith(DocumentAdapters.from(getResponse));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,17 +155,15 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
return observe(ElasticsearchOperationName.MULTI_GET, index, () -> {
|
||||
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
|
||||
MgetResponse<EntityAsMap> result = execute(client -> client.mget(request, EntityAsMap.class));
|
||||
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
|
||||
MgetResponse<EntityAsMap> result = execute(client -> client.mget(request, EntityAsMap.class));
|
||||
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index);
|
||||
|
||||
return DocumentAdapters.from(result).stream() //
|
||||
.map(multiGetItem -> MultiGetItem.of( //
|
||||
multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), multiGetItem.getFailure())) //
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
return DocumentAdapters.from(result).stream() //
|
||||
.map(multiGetItem -> MultiGetItem.of( //
|
||||
multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), multiGetItem.getFailure())) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -279,26 +185,22 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
public ByQueryResponse delete(DeleteQuery query, Class<?> clazz, IndexCoordinates index) {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
return observe(ElasticsearchOperationName.DELETE_BY_QUERY, index, () -> {
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
clazz, index, getRefreshPolicy());
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
clazz, index, getRefreshPolicy());
|
||||
|
||||
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
|
||||
DeleteByQueryResponse response = execute(client -> client.deleteByQuery(request));
|
||||
|
||||
return responseConverter.byQueryResponse(response);
|
||||
});
|
||||
return responseConverter.byQueryResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResponse update(UpdateQuery updateQuery, IndexCoordinates index) {
|
||||
|
||||
return observe(ElasticsearchOperationName.UPDATE, index, () -> {
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index,
|
||||
getRefreshPolicy(), routingResolver.getRouting());
|
||||
co.elastic.clients.elasticsearch.core.UpdateResponse<Document> response = execute(
|
||||
client -> client.update(request, Document.class));
|
||||
return UpdateResponse.of(result(response.result()));
|
||||
});
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
|
||||
routingResolver.getRouting());
|
||||
co.elastic.clients.elasticsearch.core.UpdateResponse<Document> response = execute(
|
||||
client -> client.update(request, Document.class));
|
||||
return UpdateResponse.of(result(response.result()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,13 +209,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(updateQuery, "updateQuery must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return observe(ElasticsearchOperationName.UPDATE_BY_QUERY, index, () -> {
|
||||
UpdateByQueryRequest request = requestConverter.documentUpdateByQueryRequest(updateQuery, index,
|
||||
getRefreshPolicy());
|
||||
UpdateByQueryRequest request = requestConverter.documentUpdateByQueryRequest(updateQuery, index,
|
||||
getRefreshPolicy());
|
||||
|
||||
UpdateByQueryResponse byQueryResponse = execute(client -> client.updateByQuery(request));
|
||||
return responseConverter.byQueryResponse(byQueryResponse);
|
||||
});
|
||||
UpdateByQueryResponse byQueryResponse = execute(client -> client.updateByQuery(request));
|
||||
return responseConverter.byQueryResponse(byQueryResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -428,14 +328,12 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return observe(ElasticsearchOperationName.COUNT, index, () -> {
|
||||
SearchRequest searchRequest = requestConverter.searchRequest(query, routingResolver.getRouting(), clazz, index,
|
||||
true);
|
||||
SearchRequest searchRequest = requestConverter.searchRequest(query, routingResolver.getRouting(), clazz, index,
|
||||
true);
|
||||
|
||||
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||
SearchResponse<EntityAsMap> searchResponse = execute(client -> client.search(searchRequest, EntityAsMap.class));
|
||||
|
||||
return searchResponse.hits().total().value();
|
||||
});
|
||||
return searchResponse.hits().total().value();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -445,13 +343,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return observe(ElasticsearchOperationName.SEARCH, index, () -> {
|
||||
if (query instanceof SearchTemplateQuery searchTemplateQuery) {
|
||||
return doSearch(searchTemplateQuery, clazz, index);
|
||||
} else {
|
||||
return doSearch(query, clazz, index);
|
||||
}
|
||||
});
|
||||
if (query instanceof SearchTemplateQuery searchTemplateQuery) {
|
||||
return doSearch(searchTemplateQuery, clazz, index);
|
||||
} else {
|
||||
return doSearch(query, clazz, index);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> SearchHits<T> doSearch(Query query, Class<T> clazz, IndexCoordinates index) {
|
||||
|
||||
+9
-8
@@ -16,14 +16,12 @@
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.Jackson3JsonpMapper;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||
import co.elastic.clients.transport.TransportOptions;
|
||||
import co.elastic.clients.transport.rest5_client.Rest5ClientOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.RequestOptions;
|
||||
import co.elastic.clients.transport.rest5_client.low_level.Rest5Client;
|
||||
import tools.jackson.databind.cfg.JsonNodeFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -34,6 +32,10 @@ import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperatio
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch
|
||||
* connection using the {@link ReactiveElasticsearchClient}. This class exposes different parts of the setup as Spring
|
||||
@@ -125,11 +127,10 @@ public abstract class ReactiveElasticsearchConfiguration extends ElasticsearchCo
|
||||
// we need to create our own objectMapper that keeps null values in order to provide the storeNullValue
|
||||
// functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get
|
||||
// into this mapper, so we can safely keep them here.
|
||||
JsonMapper jsonMapper = JsonMapper.builder()
|
||||
.enable(JsonNodeFeature.WRITE_NULL_PROPERTIES)
|
||||
.enable(JsonNodeFeature.READ_NULL_PROPERTIES)
|
||||
.build();
|
||||
return new Jackson3JsonpMapper(jsonMapper);
|
||||
var objectMapper = (new ObjectMapper())
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, false)
|
||||
.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||
return new JacksonJsonpMapper(objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+53
-176
@@ -18,13 +18,31 @@ package org.springframework.data.elasticsearch.client.elc;
|
||||
import static co.elastic.clients.util.ApiTypeHelper.*;
|
||||
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.Result;
|
||||
import co.elastic.clients.elasticsearch.core.*;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.elasticsearch.BulkFailureException;
|
||||
import org.springframework.data.elasticsearch.NoSuchIndexException;
|
||||
@@ -35,7 +53,6 @@ import org.springframework.data.elasticsearch.core.AggregationContainer;
|
||||
import org.springframework.data.elasticsearch.core.IndexedObjectInformation;
|
||||
import org.springframework.data.elasticsearch.core.MultiGetItem;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations;
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.document.Document;
|
||||
@@ -52,28 +69,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.Result;
|
||||
import co.elastic.clients.elasticsearch.core.*;
|
||||
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
|
||||
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
|
||||
import co.elastic.clients.json.JsonpMapper;
|
||||
import co.elastic.clients.transport.Version;
|
||||
import co.elastic.clients.transport.endpoints.BooleanResponse;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations} using the new
|
||||
* Elasticsearch client.
|
||||
@@ -81,15 +76,12 @@ import reactor.util.function.Tuple2;
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Illia Ulianov
|
||||
* @author Junghoon Ban
|
||||
* @author maryantocinn
|
||||
* @since 4.4
|
||||
*/
|
||||
public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearchTemplate {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(ReactiveElasticsearchTemplate.class);
|
||||
|
||||
@Nullable private ElasticsearchObservationConvention observationConvention;
|
||||
|
||||
private final ReactiveElasticsearchClient client;
|
||||
private final ReactiveElasticsearchSqlClient sqlClient;
|
||||
private final RequestConverter requestConverter;
|
||||
@@ -110,105 +102,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
exceptionTranslator = new ElasticsearchExceptionTranslator(jsonpMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
super.setApplicationContext(applicationContext);
|
||||
|
||||
if (observationRegistry == ObservationRegistry.NOOP) {
|
||||
applicationContext.getBeanProvider(ObservationRegistry.class).ifAvailable(this::setObservationRegistry);
|
||||
}
|
||||
|
||||
if (observationConvention == null) {
|
||||
applicationContext.getBeanProvider(ElasticsearchObservationConvention.class)
|
||||
.ifAvailable(this::setObservationConvention);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@link ElasticsearchObservationConvention} to override the default convention.
|
||||
*
|
||||
* @param observationConvention can be {@literal null}.
|
||||
* @since 6.1
|
||||
*/
|
||||
public void setObservationConvention(@Nullable ElasticsearchObservationConvention observationConvention) {
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
private <T> Mono<T> observeMono(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
Mono<T> mono) {
|
||||
return Mono.defer(() -> {
|
||||
Observation observation = createObservation(operationName, index, null);
|
||||
return mono.doOnError(observation::error) //
|
||||
.doFinally(signalType -> observation.stop())
|
||||
.contextWrite(context -> context.put(Observation.class, observation))
|
||||
.doOnSubscribe(subscription -> observation.start());
|
||||
});
|
||||
}
|
||||
|
||||
private <T> Flux<T> observeFlux(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
Flux<T> flux) {
|
||||
return Flux.defer(() -> {
|
||||
Observation observation = createObservation(operationName, index, null);
|
||||
return flux.doOnError(observation::error) //
|
||||
.doFinally(signalType -> observation.stop())
|
||||
.contextWrite(context -> context.put(Observation.class, observation))
|
||||
.doOnSubscribe(subscription -> observation.start());
|
||||
});
|
||||
}
|
||||
|
||||
private <T> Mono<T> observeMono(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
int batchSize, Mono<T> mono) {
|
||||
return Mono.defer(() -> {
|
||||
Observation observation = createObservation(operationName, index, batchSize);
|
||||
return mono.doOnError(observation::error) //
|
||||
.doFinally(signalType -> observation.stop())
|
||||
.contextWrite(context -> context.put(Observation.class, observation))
|
||||
.doOnSubscribe(subscription -> observation.start());
|
||||
});
|
||||
}
|
||||
|
||||
private Observation createObservation(ElasticsearchOperationName operationName, @Nullable IndexCoordinates index,
|
||||
@Nullable Integer batchSize) {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(operationName, index);
|
||||
context.setBatchSize(batchSize);
|
||||
|
||||
return ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(observationConvention,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, observationRegistry);
|
||||
}
|
||||
|
||||
// region Document operations
|
||||
@Override
|
||||
public <T> Mono<T> save(T entity, IndexCoordinates index) {
|
||||
return observeMono(ElasticsearchOperationName.SAVE, index, super.save(entity, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> exists(String id, IndexCoordinates index) {
|
||||
return observeMono(ElasticsearchOperationName.EXISTS, index, super.exists(id, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> delete(Object entity, IndexCoordinates index) {
|
||||
return observeMono(ElasticsearchOperationName.DELETE, index, super.delete(entity, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> delete(String id, IndexCoordinates index) {
|
||||
return observeMono(ElasticsearchOperationName.DELETE, index, super.delete(id, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<SearchHit<T>> search(Query query, Class<?> entityType, Class<T> resultType, IndexCoordinates index) {
|
||||
return observeFlux(ElasticsearchOperationName.SEARCH, index, super.search(query, entityType, resultType, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count(Query query, Class<?> entityType, IndexCoordinates index) {
|
||||
return observeMono(ElasticsearchOperationName.COUNT, index, super.count(query, entityType, index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoordinates index) {
|
||||
|
||||
@@ -230,7 +124,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
|
||||
Assert.notNull(entitiesPublisher, "entitiesPublisher must not be null!");
|
||||
|
||||
return observeFlux(ElasticsearchOperationName.BULK, index, entitiesPublisher //
|
||||
return entitiesPublisher //
|
||||
.flatMapMany(entities -> Flux.fromIterable(entities) //
|
||||
.concatMap(entity -> maybeCallbackBeforeConvert(entity, index)) //
|
||||
).collectList() //
|
||||
@@ -252,12 +146,12 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
response.index(), //
|
||||
response.seqNo(), //
|
||||
response.primaryTerm(), //
|
||||
response.version()), //
|
||||
converter, //
|
||||
response.version()),
|
||||
converter,
|
||||
routingResolver);
|
||||
return maybeCallbackAfterSave(updatedEntity, index);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -278,11 +172,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
public Mono<ByQueryResponse> delete(DeleteQuery query, Class<?> entityType, IndexCoordinates index) {
|
||||
Assert.notNull(query, "query must not be null");
|
||||
|
||||
return observeMono(ElasticsearchOperationName.DELETE_BY_QUERY, index, Mono.defer(() -> {
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
entityType, index, getRefreshPolicy());
|
||||
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
|
||||
}));
|
||||
DeleteByQueryRequest request = requestConverter.documentDeleteByQueryRequest(query, routingResolver.getRouting(),
|
||||
entityType, index, getRefreshPolicy());
|
||||
return Mono.from(execute(client -> client.deleteByQuery(request))).map(responseConverter::byQueryResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -292,15 +184,13 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
Assert.notNull(entityType, "entityType must not be null");
|
||||
Assert.notNull(index, "index must not be null");
|
||||
|
||||
return observeMono(ElasticsearchOperationName.GET, index, Mono.defer(() -> {
|
||||
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index);
|
||||
GetRequest getRequest = requestConverter.documentGetRequest(id, routingResolver.getRouting(), index);
|
||||
|
||||
Mono<GetResponse<EntityAsMap>> getResponse = Mono //
|
||||
.from(execute(client -> client.get(getRequest, EntityAsMap.class)));
|
||||
Mono<GetResponse<EntityAsMap>> getResponse = Mono
|
||||
.from(execute(client -> client.get(getRequest, EntityAsMap.class)));
|
||||
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||
return getResponse.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
||||
}));
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
|
||||
return getResponse.flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -337,15 +227,13 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
Assert.notNull(updateQuery, "UpdateQuery must not be null");
|
||||
Assert.notNull(index, "Index must not be null");
|
||||
|
||||
return observeMono(ElasticsearchOperationName.UPDATE, index, Mono.defer(() -> {
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index,
|
||||
getRefreshPolicy(), routingResolver.getRouting());
|
||||
UpdateRequest<Document, ?> request = requestConverter.documentUpdateRequest(updateQuery, index, getRefreshPolicy(),
|
||||
routingResolver.getRouting());
|
||||
|
||||
return Mono.from(execute(client -> client.update(request, Document.class))).flatMap(response -> {
|
||||
UpdateResponse.Result result = result(response.result());
|
||||
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
|
||||
});
|
||||
}));
|
||||
return Mono.from(execute(client -> client.update(request, Document.class))).flatMap(response -> {
|
||||
UpdateResponse.Result result = result(response.result());
|
||||
return result == null ? Mono.empty() : Mono.just(UpdateResponse.of(result));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -360,8 +248,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
Assert.notNull(bulkOptions, "BulkOptions must not be null");
|
||||
Assert.notNull(index, "Index must not be null");
|
||||
|
||||
return observeMono(ElasticsearchOperationName.BULK, index, queries.size(),
|
||||
doBulkOperation(queries, bulkOptions, index).then());
|
||||
return doBulkOperation(queries, bulkOptions, index).then();
|
||||
}
|
||||
|
||||
private Flux<BulkResponseItem> doBulkOperation(List<?> queries, BulkOptions bulkOptions, IndexCoordinates index) {
|
||||
@@ -424,24 +311,22 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
Assert.notNull(query, "query must not be null");
|
||||
Assert.notNull(clazz, "clazz must not be null");
|
||||
|
||||
return observeFlux(ElasticsearchOperationName.MULTI_GET, index, Flux.defer(() -> {
|
||||
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
|
||||
MgetRequest request = requestConverter.documentMgetRequest(query, clazz, index);
|
||||
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
|
||||
ReadDocumentCallback<T> callback = new ReadDocumentCallback<>(converter, clazz, index);
|
||||
|
||||
Publisher<MgetResponse<EntityAsMap>> response = execute(client -> client.mget(request, EntityAsMap.class));
|
||||
Publisher<MgetResponse<EntityAsMap>> response = execute(client -> client.mget(request, EntityAsMap.class));
|
||||
|
||||
return Mono.from(response)//
|
||||
.flatMapMany(it -> Flux.fromIterable(DocumentAdapters.from(it))) //
|
||||
.flatMap(multiGetItem -> {
|
||||
if (multiGetItem.isFailed()) {
|
||||
return Mono.just(MultiGetItem.of(null, multiGetItem.getFailure()));
|
||||
} else {
|
||||
return callback.toEntity(multiGetItem.getItem()) //
|
||||
.map(t -> MultiGetItem.of(t, multiGetItem.getFailure()));
|
||||
}
|
||||
});
|
||||
}));
|
||||
return Mono.from(response)//
|
||||
.flatMapMany(it -> Flux.fromIterable(DocumentAdapters.from(it))) //
|
||||
.flatMap(multiGetItem -> {
|
||||
if (multiGetItem.isFailed()) {
|
||||
return Mono.just(MultiGetItem.of(null, multiGetItem.getFailure()));
|
||||
} else {
|
||||
return callback.toEntity(multiGetItem.getItem()) //
|
||||
.map(t -> MultiGetItem.of(t, multiGetItem.getFailure()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// endregion
|
||||
@@ -451,14 +336,6 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
|
||||
return new ReactiveElasticsearchTemplate(client, converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeCopy(AbstractReactiveElasticsearchTemplate copy) {
|
||||
|
||||
if (copy instanceof ReactiveElasticsearchTemplate reactiveTemplate) {
|
||||
reactiveTemplate.observationConvention = this.observationConvention;
|
||||
}
|
||||
}
|
||||
|
||||
// region search operations
|
||||
|
||||
@Override
|
||||
|
||||
+2
-12
@@ -900,17 +900,8 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
}
|
||||
|
||||
SourceFilter sourceFilter = source.getSourceFilter();
|
||||
if (sourceFilter != null && (sourceFilter.getIncludes() != null || sourceFilter.getExcludes() != null)) {
|
||||
s.sourceFields(cfg -> cfg
|
||||
.filter(f -> {
|
||||
if (sourceFilter.getIncludes() != null) {
|
||||
f.includes(Arrays.asList(sourceFilter.getIncludes()));
|
||||
}
|
||||
if (sourceFilter.getExcludes() != null) {
|
||||
f.excludes(Arrays.asList(sourceFilter.getExcludes()));
|
||||
}
|
||||
return f;
|
||||
}));
|
||||
if (sourceFilter != null && sourceFilter.getIncludes() != null) {
|
||||
s.sourceFields(Arrays.asList(sourceFilter.getIncludes()));
|
||||
}
|
||||
return s;
|
||||
}) //
|
||||
@@ -1435,7 +1426,6 @@ class RequestConverter extends AbstractQueryProcessor {
|
||||
.searchType(searchType) //
|
||||
.timeout(timeStringMs(query.getTimeout())) //
|
||||
.requestCache(query.getRequestCache()) //
|
||||
.includeNamedQueriesScore(query.getIncludeNamedQueriesScore()) //
|
||||
;
|
||||
|
||||
var pointInTime = query.getPointInTime();
|
||||
|
||||
+1
-26
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -45,6 +43,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.core.query.BulkOptions;
|
||||
import org.springframework.data.elasticsearch.core.query.ByQueryResponse;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQuery;
|
||||
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
|
||||
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
|
||||
@@ -86,7 +85,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
@Nullable protected EntityCallbacks entityCallbacks;
|
||||
@Nullable protected RefreshPolicy refreshPolicy;
|
||||
protected RoutingResolver routingResolver;
|
||||
protected ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
public AbstractElasticsearchTemplate() {
|
||||
this(null);
|
||||
@@ -119,8 +117,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
copy.setRefreshPolicy(refreshPolicy);
|
||||
copy.setObservationRegistry(observationRegistry);
|
||||
customizeCopy(copy);
|
||||
|
||||
return copy;
|
||||
}
|
||||
@@ -175,27 +171,6 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
|
||||
return refreshPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ObservationRegistry} to use for recording observations.
|
||||
*
|
||||
* @param observationRegistry must not be {@literal null}.
|
||||
* @since 6.1
|
||||
*/
|
||||
public void setObservationRegistry(ObservationRegistry observationRegistry) {
|
||||
|
||||
Assert.notNull(observationRegistry, "observationRegistry must not be null");
|
||||
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses to copy additional state during {@link #copy()}. Called after all common fields have been
|
||||
* copied. The default implementation does nothing.
|
||||
*
|
||||
* @param copy the new template instance to customize
|
||||
*/
|
||||
protected void customizeCopy(AbstractElasticsearchTemplate copy) {}
|
||||
|
||||
/**
|
||||
* logs the versions of the different Elasticsearch components.
|
||||
*
|
||||
|
||||
+2
-26
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
@@ -78,7 +77,6 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
protected RoutingResolver routingResolver;
|
||||
|
||||
protected @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||
protected ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
// region Initialization
|
||||
protected AbstractReactiveElasticsearchTemplate(@Nullable ElasticsearchConverter converter) {
|
||||
@@ -111,8 +109,6 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
}
|
||||
|
||||
copy.setRoutingResolver(routingResolver);
|
||||
copy.setObservationRegistry(observationRegistry);
|
||||
customizeCopy(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -166,27 +162,6 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ObservationRegistry} to use for recording observations.
|
||||
*
|
||||
* @param observationRegistry must not be {@literal null}.
|
||||
* @since 6.1
|
||||
*/
|
||||
public void setObservationRegistry(ObservationRegistry observationRegistry) {
|
||||
|
||||
Assert.notNull(observationRegistry, "observationRegistry must not be null");
|
||||
|
||||
this.observationRegistry = observationRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses to copy additional state during {@link #copy()}. Called after all common fields have been
|
||||
* copied. The default implementation does nothing.
|
||||
*
|
||||
* @param copy the new template instance to customize
|
||||
*/
|
||||
protected void customizeCopy(AbstractReactiveElasticsearchTemplate copy) {}
|
||||
|
||||
/**
|
||||
* logs the versions of the different Elasticsearch components.
|
||||
*
|
||||
@@ -284,7 +259,8 @@ abstract public class AbstractReactiveElasticsearchTemplate
|
||||
sink.tryEmitComplete();
|
||||
}
|
||||
})
|
||||
.subscribe(v -> {}, error -> {
|
||||
.subscribe(v -> {
|
||||
}, error -> {
|
||||
if (subscription != null) {
|
||||
subscription.cancel();
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.document;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@@ -88,7 +87,7 @@ public interface Document extends StringObjectMap<Document> {
|
||||
clear();
|
||||
try {
|
||||
putAll(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json));
|
||||
} catch (JacksonException e) {
|
||||
} catch (IOException e) {
|
||||
throw new ConversionException("Cannot parse JSON", e);
|
||||
}
|
||||
return this;
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.document;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -28,6 +25,9 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* {@link Document} implementation backed by a {@link LinkedHashMap}.
|
||||
*
|
||||
@@ -344,7 +344,7 @@ class MapDocument implements Document {
|
||||
public String toJson() {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(this);
|
||||
} catch (JacksonException e) {
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new MappingException("Cannot render document to JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-13
@@ -1,25 +1,26 @@
|
||||
package org.springframework.data.elasticsearch.core.geo;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
import tools.jackson.databind.ValueSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import org.springframework.data.geo.Point;
|
||||
|
||||
class PointSerializer extends ValueSerializer<Point> {
|
||||
class PointSerializer extends JsonSerializer<Point> {
|
||||
@Override
|
||||
public void serialize(Point value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
|
||||
gen.writePOJO(GeoPoint.fromPoint(value));
|
||||
public void serialize(Point value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeObject(GeoPoint.fromPoint(value));
|
||||
}
|
||||
}
|
||||
|
||||
class PointDeserializer extends ValueDeserializer<Point> {
|
||||
class PointDeserializer extends JsonDeserializer<Point> {
|
||||
@Override
|
||||
public Point deserialize(JsonParser p, DeserializationContext context) throws JacksonException {
|
||||
public Point deserialize(JsonParser p, DeserializationContext context) throws IOException {
|
||||
return GeoPoint.toPoint(p.readValueAs(GeoPoint.class));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.index;
|
||||
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
|
||||
+12
-17
@@ -18,13 +18,6 @@ package org.springframework.data.elasticsearch.core.index;
|
||||
import static org.springframework.data.elasticsearch.core.index.MappingParameters.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
import tools.jackson.databind.node.StringNode;
|
||||
import tools.jackson.databind.util.RawValue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -38,6 +31,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.core.TypeInformation;
|
||||
@@ -54,6 +48,13 @@ import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import com.fasterxml.jackson.databind.util.RawValue;
|
||||
|
||||
/**
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
@@ -111,15 +112,9 @@ public class MappingBuilder {
|
||||
|
||||
protected final ElasticsearchConverter elasticsearchConverter;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final MappingParametersCustomizer customizer;
|
||||
|
||||
public MappingBuilder(ElasticsearchConverter elasticsearchConverter) {
|
||||
this(elasticsearchConverter, MappingParameters::from);
|
||||
}
|
||||
|
||||
public MappingBuilder(ElasticsearchConverter elasticsearchConverter, MappingParametersCustomizer customizer) {
|
||||
this.elasticsearchConverter = elasticsearchConverter;
|
||||
this.customizer = customizer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,7 +184,7 @@ public class MappingBuilder {
|
||||
if (!excludeFromSource.isEmpty()) {
|
||||
ObjectNode sourceNode = objectNode.putObject(SOURCE);
|
||||
ArrayNode excludes = sourceNode.putArray(SOURCE_EXCLUDES);
|
||||
excludeFromSource.stream().map(StringNode::new).forEach(excludes::add);
|
||||
excludeFromSource.stream().map(TextNode::new).forEach(excludes::add);
|
||||
}
|
||||
|
||||
return objectMapper.writer().writeValueAsString(objectNode);
|
||||
@@ -243,7 +238,7 @@ public class MappingBuilder {
|
||||
|
||||
if (mappingAnnotation.dynamicDateFormats().length > 0) {
|
||||
objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll(Arrays.stream(mappingAnnotation.dynamicDateFormats())
|
||||
.map(StringNode::valueOf).collect(Collectors.toList()));
|
||||
.map(TextNode::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (runtimeFields != null) {
|
||||
@@ -551,7 +546,7 @@ public class MappingBuilder {
|
||||
|
||||
if (children.length > 1) {
|
||||
relationsNode.putArray(parent)
|
||||
.addAll(Arrays.stream(children).map(StringNode::valueOf).collect(Collectors.toList()));
|
||||
.addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList()));
|
||||
} else if (children.length == 1) {
|
||||
relationsNode.put(parent, children[0]);
|
||||
}
|
||||
@@ -594,7 +589,7 @@ public class MappingBuilder {
|
||||
private void addFieldMappingParameters(ObjectNode fieldNode, Annotation annotation, boolean nestedOrObjectField)
|
||||
throws IOException {
|
||||
|
||||
MappingParameters mappingParameters = customizer.from(annotation);
|
||||
MappingParameters mappingParameters = MappingParameters.from(annotation);
|
||||
|
||||
if (!nestedOrObjectField && mappingParameters.isStore()) {
|
||||
fieldNode.put(FIELD_PARAM_STORE, true);
|
||||
|
||||
+8
-139
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.index;
|
||||
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
import tools.jackson.databind.node.StringNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
@@ -31,12 +28,13 @@ import org.springframework.data.elasticsearch.annotations.*;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
|
||||
/**
|
||||
* A class to hold the mapping parameters that might be set on
|
||||
* {@link org.springframework.data.elasticsearch.annotations.Field } or
|
||||
* {@link org.springframework.data.elasticsearch.annotations.InnerField} annotation. The class allows extensibility
|
||||
* (non-final) to simplify mapping parameters customization, provided by
|
||||
* {@link org.springframework.data.elasticsearch.core.index.MappingParametersCustomizer}.
|
||||
* {@link org.springframework.data.elasticsearch.annotations.InnerField} annotation.
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Aleksei Arsenev
|
||||
@@ -44,10 +42,9 @@ import org.springframework.util.StringUtils;
|
||||
* @author Morgan Lutz
|
||||
* @author Sascha Woo
|
||||
* @author Haibo Liu
|
||||
* @author Andriy Redko
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MappingParameters {
|
||||
public final class MappingParameters {
|
||||
|
||||
static final String FIELD_PARAM_COERCE = "coerce";
|
||||
static final String FIELD_PARAM_COPY_TO = "copy_to";
|
||||
@@ -140,7 +137,7 @@ public class MappingParameters {
|
||||
}
|
||||
}
|
||||
|
||||
protected MappingParameters(Field field) {
|
||||
private MappingParameters(Field field) {
|
||||
index = field.index();
|
||||
store = field.store();
|
||||
fielddata = field.fielddata();
|
||||
@@ -187,7 +184,7 @@ public class MappingParameters {
|
||||
eagerGlobalOrdinals = field.eagerGlobalOrdinals();
|
||||
}
|
||||
|
||||
protected MappingParameters(InnerField field) {
|
||||
private MappingParameters(InnerField field) {
|
||||
index = field.index();
|
||||
store = field.store();
|
||||
fielddata = field.fielddata();
|
||||
@@ -288,7 +285,7 @@ public class MappingParameters {
|
||||
|
||||
if (copyTo != null && copyTo.length > 0) {
|
||||
objectNode.putArray(FIELD_PARAM_COPY_TO)
|
||||
.addAll(Arrays.stream(copyTo).map(StringNode::valueOf).collect(Collectors.toList()));
|
||||
.addAll(Arrays.stream(copyTo).map(TextNode::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (ignoreAbove != null) {
|
||||
@@ -420,132 +417,4 @@ public class MappingParameters {
|
||||
objectNode.put(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals);
|
||||
}
|
||||
}
|
||||
|
||||
protected String analyzer() {
|
||||
return analyzer;
|
||||
}
|
||||
|
||||
protected boolean coerce() {
|
||||
return coerce;
|
||||
}
|
||||
|
||||
protected String[] copyTo() {
|
||||
return copyTo;
|
||||
}
|
||||
|
||||
protected DateFormat[] dateFormats() {
|
||||
return dateFormats;
|
||||
}
|
||||
|
||||
protected String[] dateFormatPatterns() {
|
||||
return dateFormatPatterns;
|
||||
}
|
||||
|
||||
protected boolean hasDocValues() {
|
||||
return docValues;
|
||||
}
|
||||
|
||||
protected boolean hasEagerGlobalOrdinals() {
|
||||
return eagerGlobalOrdinals;
|
||||
}
|
||||
|
||||
protected boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
protected boolean hasFielddata() {
|
||||
return fielddata;
|
||||
}
|
||||
|
||||
protected Integer ignoreAbove() {
|
||||
return ignoreAbove;
|
||||
}
|
||||
|
||||
protected boolean isIgnoreMalformed() {
|
||||
return ignoreMalformed;
|
||||
}
|
||||
|
||||
protected boolean isIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
protected IndexOptions indexOptions() {
|
||||
return indexOptions;
|
||||
}
|
||||
|
||||
protected boolean isIndexPhrases() {
|
||||
return indexPhrases;
|
||||
}
|
||||
|
||||
protected IndexPrefixes indexPrefixes() {
|
||||
return indexPrefixes;
|
||||
}
|
||||
|
||||
protected String normalizer() {
|
||||
return normalizer;
|
||||
}
|
||||
|
||||
protected boolean hasNorms() {
|
||||
return norms;
|
||||
}
|
||||
|
||||
protected Integer maxShingleSize() {
|
||||
return maxShingleSize;
|
||||
}
|
||||
|
||||
protected String nullValue() {
|
||||
return nullValue;
|
||||
}
|
||||
|
||||
protected NullValueType nullValueType() {
|
||||
return nullValueType;
|
||||
}
|
||||
|
||||
protected Integer positionIncrementGap() {
|
||||
return positionIncrementGap;
|
||||
}
|
||||
|
||||
protected boolean positiveScoreImpact() {
|
||||
return positiveScoreImpact;
|
||||
}
|
||||
|
||||
protected Integer dims() {
|
||||
return dims;
|
||||
}
|
||||
|
||||
protected String elementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
protected KnnSimilarity knnSimilarity() {
|
||||
return knnSimilarity;
|
||||
}
|
||||
|
||||
protected KnnIndexOptions knnIndexOptions() {
|
||||
return knnIndexOptions;
|
||||
}
|
||||
|
||||
protected String searchAnalyzer() {
|
||||
return searchAnalyzer;
|
||||
}
|
||||
|
||||
protected double scalingFactor() {
|
||||
return scalingFactor;
|
||||
}
|
||||
|
||||
protected String similarity() {
|
||||
return similarity;
|
||||
}
|
||||
|
||||
protected TermVector termVector() {
|
||||
return termVector;
|
||||
}
|
||||
|
||||
protected FieldType type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
protected String mappedTypeName() {
|
||||
return mappedTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.index;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* Allows to customize {@link org.springframework.data.elasticsearch.core.index.MappingParameters} that are being
|
||||
* emitted for each supported annotation. Needed by dependent projects like Spring-Data-Opensearch.
|
||||
*
|
||||
* @author Andriy Redko
|
||||
* @since 6.1.0
|
||||
*/
|
||||
public interface MappingParametersCustomizer {
|
||||
/**
|
||||
* Customize @link org.springframework.data.elasticsearch.core.index.MappingParameters} for each supported annotation.
|
||||
*
|
||||
* @param annotation supported annotation
|
||||
* @return customized @link org.springframework.data.elasticsearch.core.index.MappingParameters}
|
||||
*/
|
||||
@NonNull
|
||||
MappingParameters from(@NonNull Annotation annotation);
|
||||
}
|
||||
-5
@@ -31,7 +31,6 @@ import org.springframework.data.mapping.MappingException;
|
||||
* Subclass of {@link MappingBuilder} with specialized methods To inhibit blocking calls
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Andriy Redko
|
||||
* @since 4.3
|
||||
*/
|
||||
public class ReactiveMappingBuilder extends MappingBuilder {
|
||||
@@ -40,10 +39,6 @@ public class ReactiveMappingBuilder extends MappingBuilder {
|
||||
super(elasticsearchConverter);
|
||||
}
|
||||
|
||||
public ReactiveMappingBuilder(ElasticsearchConverter elasticsearchConverter, MappingParametersCustomizer customizer) {
|
||||
super(elasticsearchConverter, customizer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildPropertyMapping(Class<?> clazz) throws MappingException {
|
||||
throw new UnsupportedOperationException(
|
||||
|
||||
@@ -81,7 +81,6 @@ public class BaseQuery implements Query {
|
||||
protected List<IdWithRouting> idsWithRouting = Collections.emptyList();
|
||||
protected List<RuntimeField> runtimeFields = new ArrayList<>();
|
||||
@Nullable protected PointInTime pointInTime;
|
||||
@Nullable protected Boolean includeNamedQueriesScore;
|
||||
private boolean queryIsUpdatedByConverter = false;
|
||||
@Nullable private Integer reactiveBatchSize = null;
|
||||
@Nullable private Boolean allowNoIndices = null;
|
||||
@@ -124,7 +123,6 @@ public class BaseQuery implements Query {
|
||||
this.docValueFields = builder.getDocValueFields();
|
||||
this.scriptedFields = builder.getScriptedFields();
|
||||
this.runtimeFields = builder.getRuntimeFields();
|
||||
this.includeNamedQueriesScore = builder.getIncludeNamedQueriesScore();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -457,14 +455,6 @@ public class BaseQuery implements Query {
|
||||
this.requestCache = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
@Override
|
||||
public void setIncludeNamedQueriesScore(@Nullable Boolean value) {
|
||||
this.includeNamedQueriesScore = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Boolean getRequestCache() {
|
||||
@@ -490,14 +480,6 @@ public class BaseQuery implements Query {
|
||||
return indicesBoost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Boolean getIncludeNamedQueriesScore() {
|
||||
return this.includeNamedQueriesScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.0
|
||||
*/
|
||||
|
||||
@@ -71,7 +71,6 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
@Nullable Integer reactiveBatchSize;
|
||||
private final List<DocValueField> docValueFields = new ArrayList<>();
|
||||
private final List<ScriptedField> scriptedFields = new ArrayList<>();
|
||||
@Nullable private Boolean includeNamedQueryScore;
|
||||
|
||||
@Nullable
|
||||
public Sort getSort() {
|
||||
@@ -178,14 +177,6 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
return requestCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
@Nullable
|
||||
public Boolean getIncludeNamedQueriesScore(){
|
||||
return includeNamedQueryScore;
|
||||
}
|
||||
|
||||
public List<Query.IdWithRouting> getIdsWithRouting() {
|
||||
return idsWithRouting;
|
||||
}
|
||||
@@ -390,14 +381,6 @@ public abstract class BaseQueryBuilder<Q extends BaseQuery, SELF extends BaseQue
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
public SELF withIncludeNamedQueryScore (@Nullable Boolean namedQueryScore) {
|
||||
this.includeNamedQueryScore = namedQueryScore;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Ids with routing values for a multi-get request run with this query. Not used in any other searches.
|
||||
*
|
||||
|
||||
@@ -491,21 +491,6 @@ public interface Query {
|
||||
*/
|
||||
public Integer getRequestSize();
|
||||
|
||||
/**
|
||||
* Sets the include_named_queries_score value for the query. If true, the response includes the score contribution
|
||||
* from any named queries.
|
||||
*
|
||||
* @param value new value
|
||||
* @since 6.1
|
||||
*/
|
||||
void setIncludeNamedQueriesScore(@Nullable Boolean value);
|
||||
|
||||
/**
|
||||
* @return the include_named_queries_score value for this query.
|
||||
*/
|
||||
@Nullable
|
||||
Boolean getIncludeNamedQueriesScore();
|
||||
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
|
||||
+1
-2
@@ -90,8 +90,7 @@ public abstract class AbstractElasticsearchRepositoryQuery implements Repository
|
||||
|
||||
Query query = createQuery(parameters);
|
||||
|
||||
IndexCoordinates index = parameterAccessor
|
||||
.getIndexCoordinates(elasticsearchOperations.getIndexCoordinatesFor(clazz));
|
||||
IndexCoordinates index = elasticsearchOperations.getIndexCoordinatesFor(clazz);
|
||||
|
||||
Object result = null;
|
||||
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ abstract class AbstractReactiveElasticsearchRepositoryQuery implements Repositor
|
||||
evaluationContextProvider);
|
||||
|
||||
String indexName = queryMethod.getEntityInformation().getIndexName();
|
||||
IndexCoordinates index = parameterAccessor.getIndexCoordinates(IndexCoordinates.of(indexName));
|
||||
IndexCoordinates index = IndexCoordinates.of(indexName);
|
||||
|
||||
ReactiveElasticsearchQueryExecution execution = getExecution(parameterAccessor,
|
||||
new ResultProcessingConverter(processor));
|
||||
|
||||
+1
-7
@@ -17,7 +17,6 @@ package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.core.TypeInformation;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.core.query.RuntimeField;
|
||||
import org.springframework.data.elasticsearch.core.query.ScriptedField;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
@@ -43,8 +42,7 @@ public class ElasticsearchParameter extends Parameter {
|
||||
|
||||
@Override
|
||||
public boolean isSpecialParameter() {
|
||||
return super.isSpecialParameter() || isScriptedFieldParameter() || isRuntimeFieldParameter()
|
||||
|| isIndexCoordinatesParameter();
|
||||
return super.isSpecialParameter() || isScriptedFieldParameter() || isRuntimeFieldParameter();
|
||||
}
|
||||
|
||||
public Boolean isScriptedFieldParameter() {
|
||||
@@ -54,8 +52,4 @@ public class ElasticsearchParameter extends Parameter {
|
||||
public Boolean isRuntimeFieldParameter() {
|
||||
return RuntimeField.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
public Boolean isIndexCoordinatesParameter() {
|
||||
return IndexCoordinates.class.isAssignableFrom(getType());
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
|
||||
/**
|
||||
@@ -30,11 +29,4 @@ public interface ElasticsearchParameterAccessor extends ParameterAccessor {
|
||||
* @return
|
||||
*/
|
||||
Object[] getValues();
|
||||
|
||||
/**
|
||||
* If there is a parameter of type IndexCoordinates, this parameter value is returned, otherwise the defaults value
|
||||
*
|
||||
* @param defaults default value
|
||||
*/
|
||||
IndexCoordinates getIndexCoordinates(IndexCoordinates defaults);
|
||||
}
|
||||
|
||||
+1
-28
@@ -29,11 +29,10 @@ import org.springframework.data.repository.query.ParametersSource;
|
||||
* @since 3.2
|
||||
*/
|
||||
public class ElasticsearchParameters extends Parameters<ElasticsearchParameters, ElasticsearchParameter> {
|
||||
|
||||
private final List<ElasticsearchParameter> scriptedFields = new ArrayList<>();
|
||||
private final List<ElasticsearchParameter> runtimeFields = new ArrayList<>();
|
||||
|
||||
private final int indexCoordinatesIndex;
|
||||
|
||||
public ElasticsearchParameters(ParametersSource parametersSource) {
|
||||
|
||||
super(parametersSource,
|
||||
@@ -54,23 +53,6 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
|
||||
runtimeFields.add(parameter);
|
||||
}
|
||||
}
|
||||
this.indexCoordinatesIndex = initIndexCoordinatesIndex();
|
||||
}
|
||||
|
||||
private int initIndexCoordinatesIndex() {
|
||||
int indexCoordinatesIndex = -1;
|
||||
int index = 0;
|
||||
for (ElasticsearchParameter parameter : this) {
|
||||
if (parameter.isIndexCoordinatesParameter()) {
|
||||
if (indexCoordinatesIndex != -1) {
|
||||
throw new IllegalArgumentException(this + " can only contain at most one IndexCoordinates parameter.");
|
||||
} else {
|
||||
indexCoordinatesIndex = index;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return indexCoordinatesIndex;
|
||||
}
|
||||
|
||||
private ElasticsearchParameter parameterFactory(MethodParameter methodParameter, TypeInformation<?> domainType) {
|
||||
@@ -79,7 +61,6 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
|
||||
|
||||
private ElasticsearchParameters(List<ElasticsearchParameter> parameters) {
|
||||
super(parameters);
|
||||
this.indexCoordinatesIndex = initIndexCoordinatesIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,12 +75,4 @@ public class ElasticsearchParameters extends Parameters<ElasticsearchParameters,
|
||||
List<ElasticsearchParameter> getRuntimeFields() {
|
||||
return runtimeFields;
|
||||
}
|
||||
|
||||
public boolean hasIndexCoordinatesParameter() {
|
||||
return this.indexCoordinatesIndex != -1;
|
||||
}
|
||||
|
||||
public int getIndexCoordinatesIndex() {
|
||||
return indexCoordinatesIndex;
|
||||
}
|
||||
}
|
||||
|
||||
-11
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,6 @@ public class ElasticsearchParametersParameterAccessor extends ParametersParamete
|
||||
implements ElasticsearchParameterAccessor {
|
||||
|
||||
private final Object[] values;
|
||||
private final ElasticsearchParameters eleasticSearchParameters;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ElasticsearchParametersParameterAccessor}.
|
||||
@@ -38,19 +36,10 @@ public class ElasticsearchParametersParameterAccessor extends ParametersParamete
|
||||
|
||||
super(method.getParameters(), values);
|
||||
this.values = values;
|
||||
this.eleasticSearchParameters = method.getParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexCoordinates getIndexCoordinates(IndexCoordinates defaults) {
|
||||
if (!eleasticSearchParameters.hasIndexCoordinatesParameter()) {
|
||||
return defaults;
|
||||
}
|
||||
return (IndexCoordinates) getValues()[eleasticSearchParameters.getIndexCoordinatesIndex()];
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* ElasticsearchPartQuery
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Kevin Leturc
|
||||
* @author Mark Paluch
|
||||
* @author Rasmus Faber-Espensen
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @deprecated since 5.5, use {@link RepositoryPartQuery} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class ElasticsearchPartQuery extends RepositoryPartQuery {
|
||||
public ElasticsearchPartQuery(ElasticsearchQueryMethod method, ElasticsearchOperations elasticsearchOperations,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
super(method, elasticsearchOperations, valueExpressionDelegate);
|
||||
}
|
||||
}
|
||||
-5
@@ -373,11 +373,6 @@ public class ElasticsearchQueryMethod extends QueryMethod {
|
||||
return fieldNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchParameters getParameters() {
|
||||
return (ElasticsearchParameters) super.getParameters();
|
||||
}
|
||||
|
||||
// region Copied from QueryMethod base class
|
||||
/*
|
||||
* Copied from the QueryMethod class adding support for collections of SearchHit instances. No static method here.
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* ElasticsearchStringQuery
|
||||
*
|
||||
* @author Rizwan Idrees
|
||||
* @author Mohsin Husen
|
||||
* @author Mark Paluch
|
||||
* @author Taylor Ono
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Haibo Liu
|
||||
* @deprecated since 5.5, use {@link RepositoryStringQuery}
|
||||
*/
|
||||
@Deprecated(since = "5.5", forRemoval = true)
|
||||
public class ElasticsearchStringQuery extends RepositoryStringQuery {
|
||||
public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations,
|
||||
String queryString, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
super(queryMethod, elasticsearchOperations, queryString, valueExpressionDelegate);
|
||||
}
|
||||
}
|
||||
+5
@@ -136,6 +136,11 @@ public class ReactiveElasticsearchQueryMethod extends ElasticsearchQueryMethod {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElasticsearchParameters getParameters() {
|
||||
return (ElasticsearchParameters) super.getParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAllowedGenericType(ParameterizedType methodGenericReturnType) {
|
||||
return super.isAllowedGenericType(methodGenericReturnType)
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2019-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.repository.query;
|
||||
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Taylor Ono
|
||||
* @author Haibo Liu
|
||||
* @since 3.2
|
||||
* @deprecated since 5.5, use {@link ReactiveRepositoryStringQuery}
|
||||
*/
|
||||
@Deprecated(since = "5.5", forRemoval = true)
|
||||
public class ReactiveElasticsearchStringQuery extends ReactiveRepositoryStringQuery {
|
||||
|
||||
public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
super(queryMethod, operations, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQueryMethod queryMethod,
|
||||
ReactiveElasticsearchOperations operations, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
super(query, queryMethod, operations, valueExpressionDelegate);
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.support;
|
||||
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -27,6 +25,9 @@ import java.util.function.BiConsumer;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@@ -47,7 +48,7 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
||||
public String toJson() {
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(this);
|
||||
} catch (JacksonException e) {
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Cannot render document to JSON", e);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +61,7 @@ public class DefaultStringObjectMap<T extends StringObjectMap<T>> implements Str
|
||||
delegate.clear();
|
||||
try {
|
||||
delegate.putAll(OBJECT_MAPPER.readerFor(Map.class).readValue(json));
|
||||
} catch (JacksonException e) {
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Cannot parse JSON", e);
|
||||
}
|
||||
return (T) this;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Spring Data Elasticsearch 6.1 RC1 (2026.0.0)
|
||||
Spring Data Elasticsearch 6.0.6 (2025.1.6)
|
||||
Copyright (c) [2013-2022] Pivotal Software, Inc.
|
||||
|
||||
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
||||
@@ -19,3 +19,5 @@ conditions of the subcomponent's license, as noted in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.Observation;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultElasticsearchObservationConvention}.
|
||||
*
|
||||
* @author maryantocinn
|
||||
*/
|
||||
class DefaultElasticsearchObservationConventionTests {
|
||||
|
||||
private final DefaultElasticsearchObservationConvention convention = DefaultElasticsearchObservationConvention.INSTANCE;
|
||||
|
||||
@Test
|
||||
@DisplayName("should return observation name matching ObservationDocumentation")
|
||||
void shouldReturnCorrectName() {
|
||||
assertThat(convention.getName()).isEqualTo("spring.data.elasticsearch.command");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should support ElasticsearchObservationContext")
|
||||
void shouldSupportElasticsearchContext() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("my-index"));
|
||||
|
||||
assertThat(convention.supportsContext(context)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not support unrelated context")
|
||||
void shouldNotSupportUnrelatedContext() {
|
||||
|
||||
Observation.Context otherContext = new Observation.Context();
|
||||
|
||||
assertThat(convention.supportsContext(otherContext)).isFalse();
|
||||
}
|
||||
|
||||
// region contextual name
|
||||
|
||||
@Test
|
||||
@DisplayName("contextual name should be 'operation index' when index is present")
|
||||
void contextualNameWithIndex() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
assertThat(convention.getContextualName(context)).isEqualTo("search products");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contextual name should be just the operation when index is null")
|
||||
void contextualNameWithoutIndex() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
null);
|
||||
|
||||
assertThat(convention.getContextualName(context)).isEqualTo("search");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contextual name should include comma-joined indices for multi-index operations")
|
||||
void contextualNameWithMultipleIndices() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("index-a", "index-b"));
|
||||
|
||||
assertThat(convention.getContextualName(context)).isEqualTo("search index-a,index-b");
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region low cardinality key values
|
||||
|
||||
@Test
|
||||
@DisplayName("should always include spring.data.operation")
|
||||
void shouldIncludeRequiredKeyValues() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.GET, null);
|
||||
|
||||
KeyValues keyValues = convention.getLowCardinalityKeyValues(context);
|
||||
|
||||
assertThat(keyValues).contains(KeyValue.of("spring.data.operation", "get"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should include spring.data.collection when index is present")
|
||||
void shouldIncludeCollectionWhenIndexPresent() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
KeyValues keyValues = convention.getLowCardinalityKeyValues(context);
|
||||
|
||||
assertThat(keyValues).contains(KeyValue.of("spring.data.collection", "products"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not include spring.data.collection when index is null")
|
||||
void shouldNotIncludeCollectionWhenNull() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
null);
|
||||
|
||||
KeyValues keyValues = convention.getLowCardinalityKeyValues(context);
|
||||
|
||||
assertThat(keyValues.stream().map(KeyValue::getKey)).doesNotContain("spring.data.collection");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should include spring.data.batch.size as high cardinality when batch size is set")
|
||||
void shouldIncludeBatchSizeAsHighCardinality() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("products"));
|
||||
context.setBatchSize(5);
|
||||
|
||||
KeyValues highCardValues = convention.getHighCardinalityKeyValues(context);
|
||||
|
||||
assertThat(highCardValues).contains(KeyValue.of("spring.data.batch.size", "5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should not include spring.data.batch.size in low cardinality key values")
|
||||
void shouldNotIncludeBatchSizeInLowCardinality() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("products"));
|
||||
context.setBatchSize(5);
|
||||
|
||||
KeyValues keyValues = convention.getLowCardinalityKeyValues(context);
|
||||
|
||||
assertThat(keyValues.stream().map(KeyValue::getKey)).doesNotContain("spring.data.batch.size");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should return empty high cardinality key values when batch size is null")
|
||||
void shouldReturnEmptyHighCardinalityWhenNoBatchSize() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
KeyValues highCardValues = convention.getHighCardinalityKeyValues(context);
|
||||
|
||||
assertThat(highCardValues.stream().map(KeyValue::getKey)).doesNotContain("spring.data.batch.size");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should produce correct key values for a full bulk operation")
|
||||
void shouldProduceCorrectKeyValuesForBulk() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("orders"));
|
||||
context.setBatchSize(100);
|
||||
|
||||
KeyValues lowCardValues = convention.getLowCardinalityKeyValues(context);
|
||||
KeyValues highCardValues = convention.getHighCardinalityKeyValues(context);
|
||||
|
||||
assertThat(lowCardValues).contains(KeyValue.of("spring.data.operation", "bulk"),
|
||||
KeyValue.of("spring.data.collection", "orders"));
|
||||
assertThat(highCardValues).contains(KeyValue.of("spring.data.batch.size", "100"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should produce correct key values for each operation name")
|
||||
void shouldProduceCorrectOperationNames() {
|
||||
|
||||
for (ElasticsearchOperationName operationName : ElasticsearchOperationName.values()) {
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(operationName,
|
||||
IndexCoordinates.of("test-index"));
|
||||
|
||||
KeyValues keyValues = convention.getLowCardinalityKeyValues(context);
|
||||
|
||||
assertThat(keyValues).contains(KeyValue.of("spring.data.operation", operationName.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ElasticsearchObservationContext}.
|
||||
*
|
||||
* @author maryantocinn
|
||||
*/
|
||||
class ElasticsearchObservationContextTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("should carry operation name and index coordinates")
|
||||
void shouldCarryOperationNameAndIndex() {
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("my-index");
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
index);
|
||||
|
||||
assertThat(context.getOperationName()).isEqualTo(ElasticsearchOperationName.SEARCH);
|
||||
assertThat(context.getIndexCoordinates()).isEqualTo(index);
|
||||
assertThat(context.getIndexName()).isEqualTo("my-index");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should return null index name when index coordinates are null")
|
||||
void shouldReturnNullIndexNameWhenNull() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
null);
|
||||
|
||||
assertThat(context.getIndexCoordinates()).isNull();
|
||||
assertThat(context.getIndexName()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should join multiple index names with comma")
|
||||
void shouldJoinMultipleIndexNames() {
|
||||
|
||||
IndexCoordinates index = IndexCoordinates.of("index-a", "index-b", "index-c");
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
index);
|
||||
|
||||
assertThat(context.getIndexName()).isEqualTo("index-a,index-b,index-c");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should store and retrieve batch size")
|
||||
void shouldStoreAndRetrieveBatchSize() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("my-index"));
|
||||
|
||||
assertThat(context.getBatchSize()).isNull();
|
||||
|
||||
context.setBatchSize(42);
|
||||
|
||||
assertThat(context.getBatchSize()).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should allow null batch size")
|
||||
void shouldAllowNullBatchSize() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("my-index"));
|
||||
context.setBatchSize(10);
|
||||
context.setBatchSize(null);
|
||||
|
||||
assertThat(context.getBatchSize()).isNull();
|
||||
}
|
||||
}
|
||||
-207
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.micrometer.common.docs.KeyName;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticsearchObservation} and the end-to-end observation lifecycle.
|
||||
*
|
||||
* @author maryantocinn
|
||||
*/
|
||||
class ElasticsearchObservationDocumentationTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("observation name should be 'spring.data.elasticsearch.command'")
|
||||
void shouldHaveCorrectObservationName() {
|
||||
assertThat(ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.getName()).isEqualTo(
|
||||
"spring.data.elasticsearch.command");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("default convention should be DefaultElasticsearchObservationConvention")
|
||||
void shouldHaveCorrectDefaultConvention() {
|
||||
assertThat(ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.getDefaultConvention()).isEqualTo(
|
||||
DefaultElasticsearchObservationConvention.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should declare all expected low cardinality key names")
|
||||
void shouldDeclareExpectedLowCardinalityKeyNames() {
|
||||
|
||||
KeyName[] keyNames = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.getLowCardinalityKeyNames();
|
||||
List<String> keyStrings = Arrays.stream(keyNames).map(KeyName::asString).collect(Collectors.toList());
|
||||
|
||||
assertThat(keyStrings).containsExactlyInAnyOrder("spring.data.operation", "spring.data.collection");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should declare spring.data.batch.size as high cardinality key name")
|
||||
void shouldDeclareHighCardinalityKeyNames() {
|
||||
|
||||
KeyName[] keyNames = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.getHighCardinalityKeyNames();
|
||||
List<String> keyStrings = Arrays.stream(keyNames).map(KeyName::asString).collect(Collectors.toList());
|
||||
|
||||
assertThat(keyStrings).containsExactly("spring.data.batch.size");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation with correct key values using TestObservationRegistry")
|
||||
void shouldRecordObservationWithKeyValues() {
|
||||
|
||||
TestObservationRegistry registry = TestObservationRegistry.create();
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(null,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, registry);
|
||||
|
||||
observation.start();
|
||||
observation.stop();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(registry).doesNotHaveAnyRemainingCurrentObservation()
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command").that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "search")
|
||||
.hasLowCardinalityKeyValue("spring.data.collection", "products").hasContextualNameEqualTo("search products");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation without index when null")
|
||||
void shouldRecordObservationWithoutIndex() {
|
||||
|
||||
TestObservationRegistry registry = TestObservationRegistry.create();
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.DELETE,
|
||||
null);
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(null,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, registry);
|
||||
|
||||
observation.start();
|
||||
observation.stop();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(registry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command").that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "delete")
|
||||
.doesNotHaveLowCardinalityKeyValue("spring.data.collection", "products").hasContextualNameEqualTo("delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation with batch size for bulk operations")
|
||||
void shouldRecordObservationWithBatchSize() {
|
||||
|
||||
TestObservationRegistry registry = TestObservationRegistry.create();
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.BULK,
|
||||
IndexCoordinates.of("logs"));
|
||||
context.setBatchSize(25);
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(null,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, registry);
|
||||
|
||||
observation.start();
|
||||
observation.stop();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(registry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command").that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "bulk")
|
||||
.hasHighCardinalityKeyValue("spring.data.batch.size", "25").hasContextualNameEqualTo("bulk logs");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record error on observation")
|
||||
void shouldRecordErrorOnObservation() {
|
||||
|
||||
TestObservationRegistry registry = TestObservationRegistry.create();
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(null,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, registry);
|
||||
|
||||
RuntimeException error = new RuntimeException("connection refused");
|
||||
observation.start();
|
||||
observation.error(error);
|
||||
observation.stop();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(registry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command").that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "search").hasBeenStopped();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("NOOP registry should not record any observations")
|
||||
void noopRegistryShouldNotRecord() {
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(null,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, ObservationRegistry.NOOP);
|
||||
|
||||
observation.start();
|
||||
observation.stop();
|
||||
|
||||
assertThat(observation.isNoop()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("custom convention should override default key values")
|
||||
void customConventionShouldOverride() {
|
||||
|
||||
TestObservationRegistry registry = TestObservationRegistry.create();
|
||||
|
||||
ElasticsearchObservationConvention customConvention = new ElasticsearchObservationConvention() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "custom.elasticsearch.command";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ElasticsearchObservationContext context) {
|
||||
return "custom " + context.getOperationName().getValue();
|
||||
}
|
||||
};
|
||||
|
||||
ElasticsearchObservationContext context = new ElasticsearchObservationContext(ElasticsearchOperationName.SEARCH,
|
||||
IndexCoordinates.of("products"));
|
||||
|
||||
Observation observation = ElasticsearchObservation.ELASTICSEARCH_COMMAND_OBSERVATION.observation(customConvention,
|
||||
DefaultElasticsearchObservationConvention.INSTANCE, () -> context, registry);
|
||||
|
||||
observation.start();
|
||||
observation.stop();
|
||||
|
||||
TestObservationRegistryAssert.assertThat(registry).hasObservationWithNameEqualTo("custom.elasticsearch.command")
|
||||
.that().hasContextualNameEqualTo("custom search");
|
||||
}
|
||||
}
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026-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.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.client.elc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
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.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests verifying that Micrometer observations are recorded for Spring Data Elasticsearch template
|
||||
* operations when a {@link TestObservationRegistry} is wired into the application context.
|
||||
*
|
||||
* @author maryantocinn
|
||||
* @since 6.1
|
||||
*/
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ObservabilityIntegrationTests.Config.class })
|
||||
@DisplayName("Observability Integration Tests")
|
||||
class ObservabilityIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchTemplateConfiguration.class })
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("observability-it");
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestObservationRegistry observationRegistry() {
|
||||
return TestObservationRegistry.create();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired private ElasticsearchOperations operations;
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
@Autowired private TestObservationRegistry observationRegistry;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SampleEntity.class).createWithMapping();
|
||||
observationRegistry.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation for save operation")
|
||||
void shouldRecordObservationForSave() {
|
||||
|
||||
SampleEntity entity = new SampleEntity();
|
||||
entity.setId("1");
|
||||
entity.setMessage("hello");
|
||||
|
||||
operations.save(entity);
|
||||
|
||||
TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command")
|
||||
.that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "save")
|
||||
.hasBeenStopped();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation for search operation")
|
||||
void shouldRecordObservationForSearch() {
|
||||
|
||||
SampleEntity entity = new SampleEntity();
|
||||
entity.setId("1");
|
||||
entity.setMessage("hello");
|
||||
operations.save(entity);
|
||||
observationRegistry.clear();
|
||||
|
||||
SearchHits<SampleEntity> hits = operations.search(operations.matchAllQuery(), SampleEntity.class);
|
||||
|
||||
assertThat(hits.getTotalHits()).isGreaterThanOrEqualTo(1);
|
||||
TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command")
|
||||
.that()
|
||||
.hasLowCardinalityKeyValue("spring.data.operation", "search")
|
||||
.hasBeenStopped();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should record observation with collection name")
|
||||
void shouldRecordObservationWithCollectionName() {
|
||||
|
||||
SampleEntity entity = new SampleEntity();
|
||||
entity.setId("1");
|
||||
entity.setMessage("hello");
|
||||
|
||||
operations.save(entity);
|
||||
|
||||
TestObservationRegistryAssert.assertThat(observationRegistry)
|
||||
.hasObservationWithNameEqualTo("spring.data.elasticsearch.command")
|
||||
.that()
|
||||
.hasLowCardinalityKeyValue("spring.data.collection", indexNameProvider.indexName());
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SampleEntity {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Text) private String message;
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(@Nullable String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,11 @@ import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationS
|
||||
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
|
||||
import org.springframework.data.elasticsearch.core.index.MappingBuilder;
|
||||
import org.springframework.data.elasticsearch.core.index.MappingParametersCustomizer;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.util.Lazy;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Andriy Redko
|
||||
*/
|
||||
public abstract class MappingContextBaseTests {
|
||||
|
||||
@@ -44,10 +42,6 @@ public abstract class MappingContextBaseTests {
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
final protected MappingBuilder getMappingBuilder(MappingParametersCustomizer customizer) {
|
||||
return new MappingBuilder(elasticsearchConverter.get(), customizer);
|
||||
}
|
||||
|
||||
final protected MappingBuilder getMappingBuilder() {
|
||||
return new MappingBuilder(elasticsearchConverter.get());
|
||||
}
|
||||
|
||||
-32
@@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -50,8 +49,6 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.geo.Polygon;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* @author Stuart Stevenson
|
||||
* @author Jakub Vavrik
|
||||
@@ -1342,35 +1339,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
assertEquals(expected, result, true);
|
||||
}
|
||||
|
||||
@Test // #1700
|
||||
@DisplayName("should allow mapping parameters tion")
|
||||
void shouldAllowMappingParametersCustomization() throws JSONException {
|
||||
String expected = """
|
||||
{
|
||||
"properties": {
|
||||
"my_vector": {
|
||||
"type": "dense_vector",
|
||||
"dimensions": 16
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
final MappingParametersCustomizer customizer = annotation -> new MappingParameters((Field) annotation) {
|
||||
@Override
|
||||
public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException {
|
||||
if (type() == FieldType.Dense_Vector) {
|
||||
objectNode.put(FIELD_PARAM_TYPE, mappedTypeName());
|
||||
objectNode.put("dimensions", dims());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
String mapping = getMappingBuilder(customizer)
|
||||
.buildPropertyMapping(DenseVectorEntity.class);
|
||||
|
||||
assertEquals(expected, mapping, false);
|
||||
}
|
||||
// region entities
|
||||
|
||||
@Document(indexName = "ignore-above-index")
|
||||
|
||||
-64
@@ -19,11 +19,9 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import co.elastic.clients.elasticsearch._types.GeoHashPrecision;
|
||||
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
|
||||
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.data.Offset;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
@@ -174,68 +172,6 @@ public abstract class NativeQueryIntegrationTests {
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
|
||||
}
|
||||
|
||||
@Test // #3248
|
||||
@DisplayName("should be able to use includeNamedQueriesScore in a NativeQuery")
|
||||
void shouldBeAbleToUseIncludeQueriesScoreInANativeQuery() {
|
||||
|
||||
var entity = new SampleEntity();
|
||||
entity.setId("7");
|
||||
entity.setText("seven");
|
||||
operations.save(entity);
|
||||
entity = new SampleEntity();
|
||||
entity.setId("42");
|
||||
entity.setText("matched");
|
||||
operations.save(entity);
|
||||
|
||||
var matchQuery = MatchQuery.of(m -> m.field("text")
|
||||
.query("matched")
|
||||
.queryName("namedQuery"));
|
||||
|
||||
var nativeQuery = NativeQuery.builder()
|
||||
.withQuery(matchQuery._toQuery())
|
||||
.withIncludeNamedQueryScore(true)
|
||||
.build();
|
||||
|
||||
var searchHits = operations.search(nativeQuery, SampleEntity.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
|
||||
assertThat(searchHits.getSearchHit(0).getMatchedQueries()).containsKey("namedQuery");
|
||||
assertThat(searchHits.getSearchHit(0).getMatchedQueries().get("namedQuery")).isGreaterThan(0.0);
|
||||
assertThat(searchHits.getSearchHit(0).getMatchedQueries().get("namedQuery"))
|
||||
.isCloseTo(searchHits.getMaxScore(), Offset.offset(0.01));
|
||||
}
|
||||
|
||||
@Test // #3248
|
||||
@DisplayName("should not be able to use named queries score in a NativeQuery when disabled")
|
||||
void shouldNotBeAbleToUseNamedQueriesScoreInANativeQueryWhenDisabled() {
|
||||
|
||||
var entity = new SampleEntity();
|
||||
entity.setId("7");
|
||||
entity.setText("seven");
|
||||
operations.save(entity);
|
||||
entity = new SampleEntity();
|
||||
entity.setId("42");
|
||||
entity.setText("matched");
|
||||
operations.save(entity);
|
||||
|
||||
var matchQuery = MatchQuery.of(m -> m.field("text")
|
||||
.query("matched")
|
||||
.queryName("namedQuery"));
|
||||
|
||||
var nativeQuery = NativeQuery.builder()
|
||||
.withQuery(matchQuery._toQuery())
|
||||
.withIncludeNamedQueryScore(false)
|
||||
.build();
|
||||
|
||||
var searchHits = operations.search(nativeQuery, SampleEntity.class);
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity.getId());
|
||||
assertThat(searchHits.getSearchHit(0).getMatchedQueries()).containsKey("namedQuery");
|
||||
assertThat(searchHits.getSearchHit(0).getMatchedQueries().get("namedQuery")).isNull();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SampleEntity {
|
||||
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package org.springframework.data.elasticsearch.repository.query.indexcoordinates;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@ContextConfiguration(classes = { IndexCoordinatesParameterELCIntegrationTests.Config.class })
|
||||
public class IndexCoordinatesParameterELCIntegrationTests extends IndexCoordinatesParameterIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ElasticsearchTemplateConfiguration.class })
|
||||
@EnableElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("query-index-coordinates");
|
||||
}
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
package org.springframework.data.elasticsearch.repository.query.indexcoordinates;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
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.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.ElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
|
||||
@SpringIntegrationTest
|
||||
abstract class IndexCoordinatesParameterIntegrationTests {
|
||||
@Autowired ElasticsearchOperations operations;
|
||||
@Autowired IndexNameProvider indexNameProvider;
|
||||
@Autowired RecordRepository recordRepository;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
indexNameProvider.increment();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||
}
|
||||
|
||||
@Test // #2506
|
||||
@DisplayName("should use indexcoordinates passes as repository query argument")
|
||||
void shouldUseIndexCoordinatesPassesAsRepositoryQueryArgument() {
|
||||
|
||||
var record1 = new Record("1", "one");
|
||||
var indexName1 = indexNameProvider.indexName();
|
||||
var indexCoordinates1 = IndexCoordinates.of(indexName1);
|
||||
operations.save(record1, indexCoordinates1);
|
||||
|
||||
var record2 = new Record("2", "two");
|
||||
var indexName2 = indexName1 + "second";
|
||||
var indexCoordinates2 = IndexCoordinates.of(indexName2);
|
||||
operations.save(record2, indexCoordinates2);
|
||||
|
||||
// search for record1
|
||||
var searchHits = recordRepository.findByText("one");
|
||||
assert searchHits.getTotalHits() == 1;
|
||||
searchHits = recordRepository.findByText("one", indexCoordinates2);
|
||||
assert searchHits.getTotalHits() == 0;
|
||||
|
||||
// search for record2
|
||||
searchHits = recordRepository.findByText("two");
|
||||
assert searchHits.getTotalHits() == 0;
|
||||
searchHits = recordRepository.findByText("two", indexCoordinates2);
|
||||
assert searchHits.getTotalHits() == 1;
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class Record {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Keyword) private String text;
|
||||
|
||||
public Record(@Nullable String id, @Nullable String text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public @Nullable String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(@Nullable String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
interface RecordRepository extends ElasticsearchRepository<Record, String> {
|
||||
SearchHits<Record> findByText(String text);
|
||||
|
||||
SearchHits<Record> findByText(String text, IndexCoordinates index);
|
||||
}
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package org.springframework.data.elasticsearch.repository.query.indexcoordinates;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@ContextConfiguration(classes = { ReactiveIndexCoordinatesParameterELCIntegrationTests.Config.class })
|
||||
public class ReactiveIndexCoordinatesParameterELCIntegrationTests
|
||||
extends ReactiveIndexCoordinatesParameterIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-query-index-coordinates");
|
||||
}
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
package org.springframework.data.elasticsearch.repository.query.indexcoordinates;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
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.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.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
|
||||
@SpringIntegrationTest
|
||||
abstract class ReactiveIndexCoordinatesParameterIntegrationTests {
|
||||
@Autowired ReactiveElasticsearchOperations operations;
|
||||
@Autowired IndexNameProvider indexNameProvider;
|
||||
@Autowired RecordRepository recordRepository;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
indexNameProvider.increment();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete().block();
|
||||
}
|
||||
|
||||
@Test // #2506
|
||||
@DisplayName("should use indexcoordinates passes as repository query argument")
|
||||
void shouldUseIndexCoordinatesPassesAsRepositoryQueryArgument() {
|
||||
|
||||
var record1 = new Record("1", "one");
|
||||
var indexName1 = indexNameProvider.indexName();
|
||||
var indexCoordinates1 = IndexCoordinates.of(indexName1);
|
||||
operations.save(record1, indexCoordinates1).block();
|
||||
|
||||
var record2 = new Record("2", "two");
|
||||
var indexName2 = indexName1 + "second";
|
||||
var indexCoordinates2 = IndexCoordinates.of(indexName2);
|
||||
operations.save(record2, indexCoordinates2).block();
|
||||
|
||||
// search for record1
|
||||
recordRepository.findByText("one")
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
recordRepository.findByText("one", indexCoordinates2)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
|
||||
// search for record2
|
||||
recordRepository.findByText("two")
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(0)
|
||||
.verifyComplete();
|
||||
recordRepository.findByText("two", indexCoordinates2)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class Record {
|
||||
@Nullable
|
||||
@Id private String id;
|
||||
@Nullable
|
||||
@Field(type = FieldType.Keyword) private String text;
|
||||
|
||||
public Record(@Nullable String id, @Nullable String text) {
|
||||
this.id = id;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public @Nullable String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(@Nullable String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
interface RecordRepository extends ReactiveElasticsearchRepository<Record, String> {
|
||||
Flux<SearchHit<Record>> findByText(String text);
|
||||
|
||||
Flux<SearchHit<Record>> findByText(String text, IndexCoordinates index);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
#
|
||||
#
|
||||
sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch
|
||||
sde.testcontainers.image-version=9.3.3
|
||||
sde.testcontainers.image-version=9.2.8
|
||||
#
|
||||
#
|
||||
# needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13
|
||||
|
||||
Reference in New Issue
Block a user