diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java index c46f1da0e..a1913fed9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java @@ -34,6 +34,7 @@ import org.springframework.data.elasticsearch.core.IndexInformation; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.ResourceUtil; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.*; @@ -81,6 +82,8 @@ public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate cluste this.boundClass = boundClass; this.boundIndex = null; + // cache entities metadata + refreshEntitiesMapping(); } public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate clusterTemplate, @@ -96,6 +99,8 @@ public IndicesTemplate(ElasticsearchIndicesClient client, ClusterTemplate cluste this.boundClass = null; this.boundIndex = boundIndex; + // cache entities metadata + refreshEntitiesMapping(); } protected Class checkForBoundClass() { @@ -145,6 +150,8 @@ protected boolean doCreate(IndexCoordinates indexCoordinates, Map client.create(createIndexRequest)); + // refresh cached mappings + refreshEntitiesMapping(); return Boolean.TRUE.equals(createIndexResponse.acknowledged()); } @@ -241,6 +248,45 @@ public Map getMapping() { return responseConverter.indicesGetMapping(getMappingResponse, indexCoordinates); } + @Override + public ClusterMapping getClusterMapping() { + GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(IndexCoordinates.of("*")); + GetMappingResponse getMappingResponse = execute(client -> client.getMapping(getMappingRequest)); + + return responseConverter.indicesGetMapping(getMappingResponse); + } + + /** + * Refreshes the mapping of entities. + *

+ * This method is responsible for retrieving and updating the metadata related to the entities. + */ + private void refreshEntitiesMapping() { + ClusterMapping clusterMapping = getClusterMapping(); + for (ClusterMapping.ClusterMappingEntry mappingEntry : clusterMapping) { + // Get entity + Class entity = null; + for (ElasticsearchPersistentEntity persistentEntity : this.elasticsearchConverter.getMappingContext().getPersistentEntities()) { + if (mappingEntry.getName().equals(persistentEntity.getIndexCoordinates().getIndexName())) { + entity = persistentEntity.getType(); + + break; + } + } + + if (entity == null) { + continue; + } + + if (mappingEntry.getMappings().containsKey("dynamic_templates")) { + Object dynamicTemplates = mappingEntry.getMappings().get("dynamic_templates"); + if (dynamicTemplates instanceof List value) { + getRequiredPersistentEntity(entity).buildDynamicTemplates(value); + } + } + } + } + @Override public Settings createSettings() { return createSettings(checkForBoundClass()); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java index d4c7a6e1b..34f50f9a2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java @@ -36,6 +36,7 @@ import org.springframework.data.elasticsearch.core.IndexInformation; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.ReactiveResourceUtil; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.*; @@ -218,6 +219,14 @@ public Mono getMapping() { return getMappingResponse.map(response -> responseConverter.indicesGetMapping(response, indexCoordinates)); } + @Override + public Mono getClusterMapping() { + GetMappingRequest getMappingRequest = requestConverter.indicesGetMappingRequest(IndexCoordinates.of("*")); + Mono getMappingResponse = Mono.from(execute(client -> client.getMapping(getMappingRequest))); + + return getMappingResponse.map(responseConverter::indicesGetMapping); + } + @Override public Mono createSettings() { return createSettings(checkForBoundClass()); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java index be5e7f636..dfb82a1dd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java @@ -52,6 +52,7 @@ import org.springframework.data.elasticsearch.core.IndexInformation; import org.springframework.data.elasticsearch.core.MultiGetItem; import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.index.Settings; @@ -213,6 +214,16 @@ public Document indicesGetMapping(GetMappingResponse getMappingResponse, IndexCo return Document.parse(toJson(indexMappingRecord.mappings(), jsonpMapper)); } + public ClusterMapping indicesGetMapping(GetMappingResponse getMappingResponse) { + ClusterMapping.Builder builder = ClusterMapping.builder(); + for (String indexName : getMappingResponse.result().keySet()) { + Map mappings = indicesGetMapping(getMappingResponse, IndexCoordinates.of(indexName)); + builder.withMapping(ClusterMapping.ClusterMappingEntry.builder(indexName).withMappings(mappings).build()); + } + + return builder.build(); + } + public List indicesGetIndexInformations(GetIndexResponse getIndexResponse) { Assert.notNull(getIndexResponse, "getIndexResponse must not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 7c789a5af..4275e17f8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -603,15 +602,6 @@ protected SearchDocumentResponse.EntityCreator getEntityCreator(ReadDocum // region Entity callbacks protected T maybeCallbackBeforeConvert(T entity, IndexCoordinates index) { - // get entity metadata - Map mapping = indexOps(index).getMapping(); - if (mapping.containsKey("dynamic_templates")) { - Object dynamicTemplates = mapping.get("dynamic_templates"); - if (dynamicTemplates instanceof List value) { - getRequiredPersistentEntity(entity.getClass()).buildDynamicTemplates(value); - } - } - if (entityCallbacks != null) { return entityCallbacks.callback(BeforeConvertCallback.class, entity, index); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java index d09fb3861..58375cd16 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.*; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -144,6 +145,13 @@ default boolean putMapping(Class clazz) { */ Map getMapping(); + /** + * Get mappings of all indices in a cluster. + * + * @return Retrieve the mappings for all indices within a cluster. + */ + ClusterMapping getClusterMapping(); + // endregion // region settings diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperationsAdapter.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperationsAdapter.java index 397f5146d..e3c7788d8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperationsAdapter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperationsAdapter.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.core; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import reactor.core.publisher.Mono; import java.util.List; @@ -96,6 +97,11 @@ public Map getMapping() { return Objects.requireNonNull(reactiveIndexOperations.getMapping().block()); } + @Override + public ClusterMapping getClusterMapping() { + return Objects.requireNonNull(reactiveIndexOperations.getClusterMapping().block()); + } + @Override public Settings createSettings() { return Objects.requireNonNull(reactiveIndexOperations.createSettings().block()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java index a4cc50c22..8d5df3bc0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.core; +import org.springframework.data.elasticsearch.core.cluster.ClusterMapping; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -144,6 +145,13 @@ default Mono putMapping(Class clazz) { * @return the mapping */ Mono getMapping(); + + /** + * Get mappings of all indices in a cluster. + * + * @return Retrieve the mappings for all indices within a cluster. + */ + Mono getClusterMapping(); // endregion // region settings diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterMapping.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterMapping.java new file mode 100644 index 000000000..46f21df0f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterMapping.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +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 org.jetbrains.annotations.NotNull; + +/** + * Retrieves mapping definitions of all indices in the cluster. + * + * @author Youssef Aouichaoui + * @since 5.4 + */ +public class ClusterMapping implements Iterable { + private final List mappings; + + private ClusterMapping(Builder builder) { + this.mappings = builder.mappings; + } + + @NotNull + @Override + public Iterator iterator() { + return mappings.iterator(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class ClusterMappingEntry { + private final String name; + private final Map mappings; + + private ClusterMappingEntry(Builder builder) { + this.name = builder.name; + this.mappings = builder.mappings; + } + + public String getName() { + return name; + } + + public Map getMappings() { + return Collections.unmodifiableMap(mappings); + } + + public static Builder builder(String name) { + return new Builder(name); + } + + public static class Builder { + private final String name; + private final Map mappings = new HashMap<>(); + + private Builder(String name) { + this.name = name; + } + + public Builder withMappings(Map mappings) { + this.mappings.putAll(mappings); + + return this; + } + + public ClusterMappingEntry build() { + return new ClusterMappingEntry(this); + } + } + } + + public static class Builder { + private final List mappings = new ArrayList<>(); + + private Builder() {} + + public Builder withMapping(ClusterMappingEntry entry) { + mappings.add(entry); + + return this; + } + + public ClusterMapping build() { + return new ClusterMapping(this); + } + } +}