From 422dbdda7e930deec1a4982678e2f3207697d226 Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Thu, 2 May 2024 17:27:07 +0200 Subject: [PATCH 1/5] Add support for index aliases. Signed-off-by: Youssef Aouichaoui --- .../data/elasticsearch/annotations/Alias.java | 73 ++++++ .../elasticsearch/annotations/Aliases.java | 37 +++ .../elasticsearch/annotations/Document.java | 7 + .../client/elc/RequestConverter.java | 14 ++ .../core/mapping/AliasCoordinates.java | 230 ++++++++++++++++++ .../core/mapping/IndexCoordinates.java | 18 +- .../SimpleElasticsearchPersistentEntity.java | 32 ++- .../IndexOperationsIntegrationTests.java | 58 +++++ 8 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java new file mode 100644 index 000000000..7d15fb18b --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java @@ -0,0 +1,73 @@ +/* + * 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.annotations; + + +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies an alias for the index. + * + * @author Youssef Aouichaoui + * @since 5.3 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Repeatable(Aliases.class) +public @interface Alias { + /** + * @return Index alias name. + */ + String value(); + + /** + * @return Query used to limit documents the alias can access. + */ + Query filter() default @Query; + + /** + * @return Used to route indexing operations to a specific shard. + */ + String indexRouting() default ""; + + /** + * @return Used to route indexing and search operations to a specific shard. + */ + String routing() default ""; + + /** + * @return Used to route search operations to a specific shard. + */ + String searchRouting() default ""; + + /** + * @return Is the alias hidden? + */ + boolean isHidden() default false; + + /** + * @return Is it the 'write index' for the alias? + */ + boolean isWriteIndex() default false; +} diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java new file mode 100644 index 000000000..35f4140ad --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java @@ -0,0 +1,37 @@ +/* + * 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.annotations; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Container annotation that aggregates several {@link Alias} annotations. + * + * @author Youssef Aouichaoui + * @see Alias + * @since 5.3 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Aliases { + Alias[] value(); +} diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index 391e303e0..1efae12a9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -100,6 +100,13 @@ */ boolean storeVersionInSource() default true; + /** + * Aliases for the index. + * + * @since 5.3 + */ + Alias[] aliases() default {}; + /** * @since 4.3 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index c0aafd60e..d3f8aa2ae 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -88,6 +88,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -240,9 +241,22 @@ public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); Assert.notNull(settings, "settings must not be null"); + Map aliases = new HashMap<>(); + for (AliasCoordinates aliasCoordinates : indexCoordinates.getAliases()) { + Alias alias = Alias.of(ab -> ab.filter(getQuery(aliasCoordinates.getFilter(), null)) + .routing(aliasCoordinates.getRouting()) + .indexRouting(aliasCoordinates.getIndexRouting()) + .searchRouting(aliasCoordinates.getSearchRouting()) + .isHidden(aliasCoordinates.getHidden()) + .isWriteIndex(aliasCoordinates.getWriteIndex()) + ); + aliases.put(aliasCoordinates.getAlias(), alias); + } + // note: the new client does not support the index.storeType anymore return new CreateIndexRequest.Builder() // .index(indexCoordinates.getIndexName()) // + .aliases(aliases) .settings(indexSettings(settings)) // .mappings(typeMapping(mapping)) // .build(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java new file mode 100644 index 000000000..c09e04e82 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java @@ -0,0 +1,230 @@ +/* + * 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.mapping; + +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.lang.Nullable; + +import java.util.Objects; + +/** + * Immutable Value object encapsulating index alias(es). + * + * @author Youssef Aouichaoui + * @since 5.3 + */ +public class AliasCoordinates { + /** + * Alias name for the index. + */ + private final String alias; + + /** + * Query used to limit documents the alias can access. + */ + @Nullable + private final Query filter; + + /** + * Used to route indexing operations to a specific shard. + */ + @Nullable + private final String indexRouting; + + /** + * Used to route search operations to a specific shard. + */ + @Nullable + private final String searchRouting; + + /** + * Used to route indexing and search operations to a specific shard. + */ + @Nullable + private final String routing; + + /** + * The alias is hidden? + * By default, this is set to {@code false}. + */ + @Nullable + private final Boolean isHidden; + + /** + * The index is the 'write index' for the alias? + * By default, this is set to {@code false}. + */ + @Nullable + private final Boolean isWriteIndex; + + private AliasCoordinates(Builder builder) { + this.alias = builder.alias; + + this.filter = builder.filter; + + this.indexRouting = builder.indexRouting; + this.searchRouting = builder.searchRouting; + this.routing = builder.routing; + + this.isHidden = builder.isHidden; + this.isWriteIndex = builder.isWriteIndex; + } + + public String getAlias() { + return alias; + } + + @Nullable + public Query getFilter() { + return filter; + } + + @Nullable + public String getIndexRouting() { + return indexRouting; + } + + @Nullable + public String getSearchRouting() { + return searchRouting; + } + + @Nullable + public String getRouting() { + return routing; + } + + @Nullable + public Boolean getHidden() { + return isHidden; + } + + @Nullable + public Boolean getWriteIndex() { + return isWriteIndex; + } + + public static Builder builder(String alias) { + return new Builder(alias); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AliasCoordinates that)) return false; + + return Objects.equals(alias, that.alias) && Objects.equals(filter, that.filter) + && Objects.equals(indexRouting, that.indexRouting) + && Objects.equals(searchRouting, that.searchRouting) + && Objects.equals(routing, that.routing) + && Objects.equals(isHidden, that.isHidden) + && Objects.equals(isWriteIndex, that.isWriteIndex); + } + + @Override + public int hashCode() { + return Objects.hash(alias, filter, indexRouting, searchRouting, routing, isHidden, isWriteIndex); + } + + public static class Builder { + private final String alias; + + @Nullable + private Query filter; + + @Nullable + private String indexRouting; + @Nullable + private String searchRouting; + @Nullable + private String routing; + + @Nullable + private Boolean isHidden; + @Nullable + private Boolean isWriteIndex; + + public Builder(String alias) { + this.alias = alias; + } + + /** + * Query used to limit documents the alias can access. + */ + public Builder withFilter(@Nullable Query filter) { + this.filter = filter; + + return this; + } + + /** + * Used to route indexing operations to a specific shard. + */ + public Builder withIndexRouting(@Nullable String indexRouting) { + if (indexRouting != null && !indexRouting.trim().isEmpty()) { + this.indexRouting = indexRouting; + } + + return this; + } + + /** + * Used to route search operations to a specific shard. + */ + public Builder withSearchRouting(@Nullable String searchRouting) { + if (searchRouting != null && !searchRouting.trim().isEmpty()) { + this.searchRouting = searchRouting; + } + + return this; + } + + /** + * Used to route indexing and search operations to a specific shard. + */ + public Builder withRouting(@Nullable String routing) { + if (routing != null && !routing.trim().isEmpty()) { + this.routing = routing; + } + + return this; + } + + /** + * The alias is hidden? + * By default, this is set to {@code false}. + */ + public Builder withHidden(@Nullable Boolean hidden) { + isHidden = hidden; + + return this; + } + + /** + * The index is the 'write index' for the alias? + * By default, this is set to {@code false}. + */ + public Builder withWriteIndex(@Nullable Boolean writeIndex) { + isWriteIndex = writeIndex; + + return this; + } + + public AliasCoordinates build() { + return new AliasCoordinates(this); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java index a6783cba2..50f70547f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java @@ -16,6 +16,9 @@ package org.springframework.data.elasticsearch.core.mapping; import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; import org.springframework.util.Assert; @@ -32,6 +35,7 @@ public class IndexCoordinates { public static final String TYPE = "_doc"; private final String[] indexNames; + private final Set aliases = new HashSet<>(); public static IndexCoordinates of(String... indexNames) { Assert.notNull(indexNames, "indexNames must not be null"); @@ -51,6 +55,16 @@ public String[] getIndexNames() { return Arrays.copyOf(indexNames, indexNames.length); } + public IndexCoordinates withAlias(AliasCoordinates alias) { + aliases.add(alias); + + return this; + } + + public AliasCoordinates[] getAliases() { + return aliases.toArray(AliasCoordinates[]::new); + } + /** * @since 4.2 */ @@ -61,7 +75,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; IndexCoordinates that = (IndexCoordinates) o; - return Arrays.equals(indexNames, that.indexNames); + return Arrays.equals(indexNames, that.indexNames) && Objects.equals(aliases, that.aliases); } /** @@ -69,7 +83,7 @@ public boolean equals(Object o) { */ @Override public int hashCode() { - return Arrays.hashCode(indexNames); + return Arrays.hashCode(indexNames) + aliases.hashCode(); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index f265fa13d..0c4b69635 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.elasticsearch.annotations.Alias; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Dynamic; import org.springframework.data.elasticsearch.annotations.Field; @@ -31,6 +32,8 @@ import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.StringQuery; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; @@ -135,7 +138,28 @@ private String getIndexName() { @Override public IndexCoordinates getIndexCoordinates() { - return resolve(IndexCoordinates.of(getIndexName())); + IndexCoordinates coordinates = IndexCoordinates.of(getIndexName()); + if (document != null) { + for (Alias alias : document.aliases()) { + Query query = null; + if (!alias.filter().value().isEmpty()) { + query = new StringQuery(alias.filter().value()); + } + + coordinates.withAlias( + AliasCoordinates.builder(alias.value()) + .withFilter(query) + .withIndexRouting(alias.indexRouting()) + .withSearchRouting(alias.searchRouting()) + .withRouting(alias.routing()) + .withHidden(alias.isHidden()) + .withWriteIndex(alias.isWriteIndex()) + .build() + ); + } + } + + return resolve(coordinates); } @Nullable @@ -343,7 +367,11 @@ private IndexCoordinates resolve(IndexCoordinates indexCoordinates) { resolvedNames[i] = resolve(indexName); } - return IndexCoordinates.of(resolvedNames); + IndexCoordinates coordinates = IndexCoordinates.of(resolvedNames); + for (AliasCoordinates alias : indexCoordinates.getAliases()) { + coordinates.withAlias(alias); + } + return coordinates; } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java index eb65d0ac9..849c20307 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java @@ -16,11 +16,14 @@ package org.springframework.data.elasticsearch.core.index; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.SoftAssertions; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; @@ -30,13 +33,18 @@ import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Alias; import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.annotations.Setting; +import org.springframework.data.elasticsearch.client.elc.Queries; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexInformation; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.StringQuery; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; @@ -171,6 +179,29 @@ void shouldReturnAliasDataWithGetAliasesForIndexMethod() { softly.assertAll(); } + @Test + void shouldCreateIndexWithAliases() { + // Given + indexNameProvider.increment(); + String indexName = indexNameProvider.indexName(); + indexOperations = operations.indexOps(EntityWithAliases.class); + indexOperations.createWithMapping(); + + // When + Map> aliases = indexOperations.getAliasesForIndex(indexName); + + // Then + AliasData result = aliases.values().stream().findFirst().orElse(new HashSet<>()).stream().findFirst().orElse(null); + assertThat(result).isNotNull(); + assertThat(result.getAlias()).isEqualTo("first_alias"); + assertThat(result.getFilterQuery()).asInstanceOf(InstanceOfAssertFactories.type(StringQuery.class)) + .extracting(StringQuery::getSource) + .asString() + .contains(Queries.wrapperQuery(""" + {"bool" : {"must" : {"term" : {"type" : "abc"}}}} + """).query()); + } + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(settingPath = "settings/test-settings.json") @Mapping(mappingPath = "mappings/test-mappings.json") @@ -186,4 +217,31 @@ public void setId(@Nullable String id) { this.id = id; } } + + @Document(indexName = "#{@indexNameProvider.indexName()}", aliases = { + @Alias(value = "first_alias", filter =@Query(""" + {"bool" : {"must" : {"term" : {"type" : "abc"}}}} + """)) + }) + private static class EntityWithAliases { + @Nullable private @Id String id; + @Field(type = Text) private String type; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } } From 257428d4c4fcc59f0822fb1f21e6f46336184e8b Mon Sep 17 00:00:00 2001 From: Aouichaoui Youssef <21143371+youssef3wi@users.noreply.github.com> Date: Thu, 9 May 2024 22:19:48 +0200 Subject: [PATCH 2/5] Providing a synonym for value Co-authored-by: Peter-Josef Meisch --- .../springframework/data/elasticsearch/annotations/Alias.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java index 7d15fb18b..785bef6bf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java @@ -39,7 +39,11 @@ /** * @return Index alias name. */ + @AliasFor("alias") String value(); + + @AliasFor("value") + String alias() /** * @return Query used to limit documents the alias can access. From a594580bf1e4b2da5d24ba3d4b6197555607eda8 Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Fri, 10 May 2024 13:26:30 +0200 Subject: [PATCH 3/5] Extend create index settings Signed-off-by: Youssef Aouichaoui --- .../data/elasticsearch/annotations/Alias.java | 13 +- .../elasticsearch/annotations/Filter.java | 39 ++++++ .../client/elc/IndicesTemplate.java | 26 +++- .../client/elc/ReactiveIndicesTemplate.java | 23 +++- .../client/elc/RequestConverter.java | 16 +-- .../core/mapping/AliasCoordinates.java | 2 + .../core/mapping/CreateIndexSettings.java | 117 ++++++++++++++++++ .../ElasticsearchPersistentEntity.java | 3 + .../core/mapping/IndexCoordinates.java | 18 +-- .../SimpleElasticsearchPersistentEntity.java | 26 ++-- .../IndexOperationsIntegrationTests.java | 4 +- 11 files changed, 238 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java index 785bef6bf..2718e630d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java @@ -37,18 +37,21 @@ @Repeatable(Aliases.class) public @interface Alias { /** - * @return Index alias name. + * @return Index alias name. Alias for {@link #alias}. */ @AliasFor("alias") - String value(); - + String value() default ""; + + /** + * @return Index alias name. Alias for {@link #value}. + */ @AliasFor("value") - String alias() + String alias() default ""; /** * @return Query used to limit documents the alias can access. */ - Query filter() default @Query; + Filter filter() default @Filter; /** * @return Used to route indexing operations to a specific shard. diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java new file mode 100644 index 000000000..08c5369a1 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java @@ -0,0 +1,39 @@ +/* + * 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.annotations; + + +import org.springframework.core.annotation.AliasFor; + +/** + * Query used to limit documents. + * + * @author Youssef Aouichaoui + * @since 5.3 + */ +public @interface Filter { + /** + * @return Query used to limit documents. Alias for {@link #query}. + */ + @AliasFor("query") + String value() default ""; + + /** + * @return Query used to limit documents. Alias for {@link #value}. + */ + @AliasFor("value") + String query() default ""; +} 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 ec6ea3914..3ea8f9cf9 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 @@ -21,6 +21,7 @@ import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.endpoints.BooleanResponse; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -46,6 +47,8 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; +import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.lang.Nullable; @@ -137,11 +140,15 @@ public boolean createWithMapping() { protected boolean doCreate(IndexCoordinates indexCoordinates, Map settings, @Nullable Document mapping) { - - Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); - Assert.notNull(settings, "settings must not be null"); - - CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping); + Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); + CreateIndexSettings indexSettings = CreateIndexSettings.builder() + .withIndexCoordinates(indexCoordinates) + .withAliases(aliases) + .withSettings(settings) + .withMapping(mapping) + .build(); + + CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings); CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest)); return Boolean.TRUE.equals(createIndexResponse.acknowledged()); } @@ -449,5 +456,14 @@ public IndexCoordinates getIndexCoordinates() { public IndexCoordinates getIndexCoordinatesFor(Class clazz) { return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } + + /** + * Get the {@link AliasCoordinates} of the provided class. + * + * @param clazz provided class that can be used to extract aliases. + */ + public Set getAliasesFor(Class clazz) { + return getRequiredPersistentEntity(clazz).getAliases(); + } // endregion } 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 82f21f107..67ab1cbca 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 @@ -21,9 +21,12 @@ import co.elastic.clients.elasticsearch.indices.*; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.endpoints.BooleanResponse; +import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -130,8 +133,15 @@ public Mono createWithMapping() { private Mono doCreate(IndexCoordinates indexCoordinates, Map settings, @Nullable Document mapping) { - - CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping); + Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); + CreateIndexSettings indexSettings = CreateIndexSettings.builder() + .withIndexCoordinates(indexCoordinates) + .withAliases(aliases) + .withSettings(settings) + .withMapping(mapping) + .build(); + + CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings); Mono createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest))); return createIndexResponse.map(CreateIndexResponse::acknowledged); } @@ -435,6 +445,15 @@ private IndexCoordinates getIndexCoordinatesFor(Class clazz) { return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getIndexCoordinates(); } + /** + * Get the {@link AliasCoordinates} of the provided class. + * + * @param clazz provided class that can be used to extract aliases. + */ + private Set getAliasesFor(Class clazz) { + return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getAliases(); + } + private Class checkForBoundClass() { if (boundClass == null) { throw new InvalidDataAccessApiUsageException("IndexOperations are not bound"); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index d3f8aa2ae..47c12ccb7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -89,6 +89,7 @@ import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -235,14 +236,9 @@ public ExistsRequest indicesExistsRequest(IndexCoordinates indexCoordinates) { return new ExistsRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build(); } - public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates, Map settings, - @Nullable Document mapping) { - - Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); - Assert.notNull(settings, "settings must not be null"); - + public CreateIndexRequest indicesCreateRequest(CreateIndexSettings indexSettings) { Map aliases = new HashMap<>(); - for (AliasCoordinates aliasCoordinates : indexCoordinates.getAliases()) { + for (AliasCoordinates aliasCoordinates : indexSettings.getAliasCoordinates()) { Alias alias = Alias.of(ab -> ab.filter(getQuery(aliasCoordinates.getFilter(), null)) .routing(aliasCoordinates.getRouting()) .indexRouting(aliasCoordinates.getIndexRouting()) @@ -255,10 +251,10 @@ public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates // note: the new client does not support the index.storeType anymore return new CreateIndexRequest.Builder() // - .index(indexCoordinates.getIndexName()) // + .index(indexSettings.getIndexCoordinates().getIndexName()) // .aliases(aliases) - .settings(indexSettings(settings)) // - .mappings(typeMapping(mapping)) // + .settings(indexSettings(indexSettings.getSettings())) // + .mappings(typeMapping(indexSettings.getMapping())) // .build(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java index c09e04e82..6660cc142 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java @@ -17,6 +17,7 @@ import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import java.util.Objects; @@ -158,6 +159,7 @@ public static class Builder { private Boolean isWriteIndex; public Builder(String alias) { + Assert.notNull(alias, "alias must not be null"); this.alias = alias; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java new file mode 100644 index 000000000..7af69a029 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java @@ -0,0 +1,117 @@ +/* + * 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.mapping; + +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Encapsulating index mapping fields, settings, and index alias(es). + * + * @author Youssef Aouichaoui + * @since 5.3 + */ +public class CreateIndexSettings { + private final IndexCoordinates indexCoordinates; + private final Set aliasCoordinates; + + private final Map settings; + + @Nullable + private final Document mapping; + + private CreateIndexSettings(Builder builder) { + this.indexCoordinates = builder.indexCoordinates; + this.aliasCoordinates = builder.aliasCoordinates; + + this.settings = builder.settings; + this.mapping = builder.mapping; + } + + public static Builder builder() { + return new Builder(); + } + + public IndexCoordinates getIndexCoordinates() { + return indexCoordinates; + } + + public AliasCoordinates[] getAliasCoordinates() { + return aliasCoordinates.toArray(AliasCoordinates[]::new); + } + + public Map getSettings() { + return settings; + } + + @Nullable + public Document getMapping() { + return mapping; + } + + public static class Builder { + private IndexCoordinates indexCoordinates; + private Set aliasCoordinates = new HashSet<>(); + + private Map settings; + + @Nullable + private Document mapping; + + public Builder withIndexCoordinates(IndexCoordinates indexCoordinates) { + Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); + this.indexCoordinates = indexCoordinates; + + return this; + } + + public Builder withAliasCoordinates(AliasCoordinates aliasCoordinates) { + Assert.notNull(aliasCoordinates, "aliasCoordinates must not be null"); + this.aliasCoordinates.add(aliasCoordinates); + + return this; + } + + public Builder withAliases(Set aliases) { + Assert.notNull(aliases, "aliases must not be null"); + this.aliasCoordinates.addAll(aliases); + + return this; + } + + public Builder withSettings(Map settings) { + Assert.notNull(settings, "settings must not be null"); + this.settings = settings; + + return this; + } + + public Builder withMapping(@Nullable Document mapping) { + this.mapping = mapping; + + return this; + } + + public CreateIndexSettings build() { + return new CreateIndexSettings(this); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 6d13d7eb6..5f8d66842 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -25,6 +25,8 @@ import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.lang.Nullable; +import java.util.Set; + /** * ElasticsearchPersistentEntity * @@ -41,6 +43,7 @@ public interface ElasticsearchPersistentEntity extends PersistentEntity { IndexCoordinates getIndexCoordinates(); + Set getAliases(); short getShards(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java index 50f70547f..a6783cba2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexCoordinates.java @@ -16,9 +16,6 @@ package org.springframework.data.elasticsearch.core.mapping; import java.util.Arrays; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; import org.springframework.util.Assert; @@ -35,7 +32,6 @@ public class IndexCoordinates { public static final String TYPE = "_doc"; private final String[] indexNames; - private final Set aliases = new HashSet<>(); public static IndexCoordinates of(String... indexNames) { Assert.notNull(indexNames, "indexNames must not be null"); @@ -55,16 +51,6 @@ public String[] getIndexNames() { return Arrays.copyOf(indexNames, indexNames.length); } - public IndexCoordinates withAlias(AliasCoordinates alias) { - aliases.add(alias); - - return this; - } - - public AliasCoordinates[] getAliases() { - return aliases.toArray(AliasCoordinates[]::new); - } - /** * @since 4.2 */ @@ -75,7 +61,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; IndexCoordinates that = (IndexCoordinates) o; - return Arrays.equals(indexNames, that.indexNames) && Objects.equals(aliases, that.aliases); + return Arrays.equals(indexNames, that.indexNames); } /** @@ -83,7 +69,7 @@ public boolean equals(Object o) { */ @Override public int hashCode() { - return Arrays.hashCode(indexNames) + aliases.hashCode(); + return Arrays.hashCode(indexNames); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index 0c4b69635..819a101dc 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -15,7 +15,9 @@ */ package org.springframework.data.elasticsearch.core.mapping; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @@ -138,15 +140,25 @@ private String getIndexName() { @Override public IndexCoordinates getIndexCoordinates() { - IndexCoordinates coordinates = IndexCoordinates.of(getIndexName()); + return resolve(IndexCoordinates.of(getIndexName())); + } + + @Override + public Set getAliases() { + final Set aliases = new HashSet<>(); + if (document != null) { for (Alias alias : document.aliases()) { + if (alias.value().isEmpty()) { + continue; + } + Query query = null; if (!alias.filter().value().isEmpty()) { query = new StringQuery(alias.filter().value()); } - coordinates.withAlias( + aliases.add( AliasCoordinates.builder(alias.value()) .withFilter(query) .withIndexRouting(alias.indexRouting()) @@ -154,12 +166,12 @@ public IndexCoordinates getIndexCoordinates() { .withRouting(alias.routing()) .withHidden(alias.isHidden()) .withWriteIndex(alias.isWriteIndex()) - .build() + .build() ); } } - return resolve(coordinates); + return aliases; } @Nullable @@ -367,11 +379,7 @@ private IndexCoordinates resolve(IndexCoordinates indexCoordinates) { resolvedNames[i] = resolve(indexName); } - IndexCoordinates coordinates = IndexCoordinates.of(resolvedNames); - for (AliasCoordinates alias : indexCoordinates.getAliases()) { - coordinates.withAlias(alias); - } - return coordinates; + return IndexCoordinates.of(resolvedNames); } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java index 849c20307..d308eb8f0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationsIntegrationTests.java @@ -36,8 +36,8 @@ import org.springframework.data.elasticsearch.annotations.Alias; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.Filter; import org.springframework.data.elasticsearch.annotations.Mapping; -import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.client.elc.Queries; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; @@ -219,7 +219,7 @@ public void setId(@Nullable String id) { } @Document(indexName = "#{@indexNameProvider.indexName()}", aliases = { - @Alias(value = "first_alias", filter =@Query(""" + @Alias(value = "first_alias", filter =@Filter(""" {"bool" : {"must" : {"term" : {"type" : "abc"}}}} """)) }) From da5435931ee38b92c33cf81e634d1c44e322549b Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Tue, 21 May 2024 18:21:56 +0200 Subject: [PATCH 4/5] Polishing. Signed-off-by: Youssef Aouichaoui Co-authored-by: Peter-Josef Meisch --- .../data/elasticsearch/annotations/Alias.java | 2 +- .../elasticsearch/annotations/Aliases.java | 2 +- .../elasticsearch/annotations/Document.java | 2 +- .../elasticsearch/annotations/Filter.java | 2 +- .../client/elc/IndicesTemplate.java | 11 ++-- .../client/elc/ReactiveIndicesTemplate.java | 11 ++-- .../client/elc/RequestConverter.java | 22 +++---- .../{AliasCoordinates.java => Alias.java} | 12 ++-- .../core/mapping/CreateIndexSettings.java | 30 ++++----- .../ElasticsearchPersistentEntity.java | 9 ++- .../SimpleElasticsearchPersistentEntity.java | 63 ++++++++++--------- 11 files changed, 89 insertions(+), 77 deletions(-) rename src/main/java/org/springframework/data/elasticsearch/core/mapping/{AliasCoordinates.java => Alias.java} (95%) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java index 2718e630d..0fd1e3694 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Alias.java @@ -29,7 +29,7 @@ * Identifies an alias for the index. * * @author Youssef Aouichaoui - * @since 5.3 + * @since 5.4 */ @Inherited @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java index 35f4140ad..b72108210 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Aliases.java @@ -27,7 +27,7 @@ * * @author Youssef Aouichaoui * @see Alias - * @since 5.3 + * @since 5.4 */ @Inherited @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index 1efae12a9..fbe761e4c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -103,7 +103,7 @@ /** * Aliases for the index. * - * @since 5.3 + * @since 5.4 */ Alias[] aliases() default {}; diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java index 08c5369a1..6d68e3813 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Filter.java @@ -22,7 +22,7 @@ * Query used to limit documents. * * @author Youssef Aouichaoui - * @since 5.3 + * @since 5.4 */ public @interface Filter { /** 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 3ea8f9cf9..c5a2d029c 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 @@ -47,7 +47,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; -import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.Alias; import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -140,9 +140,8 @@ public boolean createWithMapping() { protected boolean doCreate(IndexCoordinates indexCoordinates, Map settings, @Nullable Document mapping) { - Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); - CreateIndexSettings indexSettings = CreateIndexSettings.builder() - .withIndexCoordinates(indexCoordinates) + Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); + CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates) .withAliases(aliases) .withSettings(settings) .withMapping(mapping) @@ -458,11 +457,11 @@ public IndexCoordinates getIndexCoordinatesFor(Class clazz) { } /** - * Get the {@link AliasCoordinates} of the provided class. + * Get the {@link Alias} of the provided class. * * @param clazz provided class that can be used to extract aliases. */ - public Set getAliasesFor(Class clazz) { + public Set getAliasesFor(Class clazz) { return getRequiredPersistentEntity(clazz).getAliases(); } // endregion 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 67ab1cbca..1c90c7a83 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 @@ -21,7 +21,7 @@ import co.elastic.clients.elasticsearch.indices.*; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.endpoints.BooleanResponse; -import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.Alias; import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -133,9 +133,8 @@ public Mono createWithMapping() { private Mono doCreate(IndexCoordinates indexCoordinates, Map settings, @Nullable Document mapping) { - Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); - CreateIndexSettings indexSettings = CreateIndexSettings.builder() - .withIndexCoordinates(indexCoordinates) + Set aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>(); + CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates) .withAliases(aliases) .withSettings(settings) .withMapping(mapping) @@ -446,11 +445,11 @@ private IndexCoordinates getIndexCoordinatesFor(Class clazz) { } /** - * Get the {@link AliasCoordinates} of the provided class. + * Get the {@link Alias} of the provided class. * * @param clazz provided class that can be used to extract aliases. */ - private Set getAliasesFor(Class clazz) { + private Set getAliasesFor(Class clazz) { return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getAliases(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java index 47c12ccb7..aef836ad2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java @@ -88,7 +88,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; -import org.springframework.data.elasticsearch.core.mapping.AliasCoordinates; +import org.springframework.data.elasticsearch.core.mapping.Alias; import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; @@ -172,7 +172,7 @@ public co.elastic.clients.elasticsearch.cluster.PutComponentTemplateRequest clus })); } - private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) { + private co.elastic.clients.elasticsearch.indices.Alias.Builder buildAlias(AliasActionParameters parameters, co.elastic.clients.elasticsearch.indices.Alias.Builder aliasBuilder) { if (parameters.getRouting() != null) { aliasBuilder.routing(parameters.getRouting()); @@ -237,16 +237,16 @@ public ExistsRequest indicesExistsRequest(IndexCoordinates indexCoordinates) { } public CreateIndexRequest indicesCreateRequest(CreateIndexSettings indexSettings) { - Map aliases = new HashMap<>(); - for (AliasCoordinates aliasCoordinates : indexSettings.getAliasCoordinates()) { - Alias alias = Alias.of(ab -> ab.filter(getQuery(aliasCoordinates.getFilter(), null)) - .routing(aliasCoordinates.getRouting()) - .indexRouting(aliasCoordinates.getIndexRouting()) - .searchRouting(aliasCoordinates.getSearchRouting()) - .isHidden(aliasCoordinates.getHidden()) - .isWriteIndex(aliasCoordinates.getWriteIndex()) + Map aliases = new HashMap<>(); + for (Alias alias : indexSettings.getAliases()) { + co.elastic.clients.elasticsearch.indices.Alias esAlias = co.elastic.clients.elasticsearch.indices.Alias.of(ab -> ab.filter(getQuery(alias.getFilter(), null)) + .routing(alias.getRouting()) + .indexRouting(alias.getIndexRouting()) + .searchRouting(alias.getSearchRouting()) + .isHidden(alias.getHidden()) + .isWriteIndex(alias.getWriteIndex()) ); - aliases.put(aliasCoordinates.getAlias(), alias); + aliases.put(alias.getAlias(), esAlias); } // note: the new client does not support the index.storeType anymore diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/Alias.java similarity index 95% rename from src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java rename to src/main/java/org/springframework/data/elasticsearch/core/mapping/Alias.java index 6660cc142..bbaa62443 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/AliasCoordinates.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/Alias.java @@ -25,9 +25,9 @@ * Immutable Value object encapsulating index alias(es). * * @author Youssef Aouichaoui - * @since 5.3 + * @since 5.4 */ -public class AliasCoordinates { +public class Alias { /** * Alias name for the index. */ @@ -71,7 +71,7 @@ public class AliasCoordinates { @Nullable private final Boolean isWriteIndex; - private AliasCoordinates(Builder builder) { + private Alias(Builder builder) { this.alias = builder.alias; this.filter = builder.filter; @@ -125,7 +125,7 @@ public static Builder builder(String alias) { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof AliasCoordinates that)) return false; + if (!(o instanceof Alias that)) return false; return Objects.equals(alias, that.alias) && Objects.equals(filter, that.filter) && Objects.equals(indexRouting, that.indexRouting) @@ -225,8 +225,8 @@ public Builder withWriteIndex(@Nullable Boolean writeIndex) { return this; } - public AliasCoordinates build() { - return new AliasCoordinates(this); + public Alias build() { + return new Alias(this); } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java index 7af69a029..1406626dd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/CreateIndexSettings.java @@ -31,8 +31,9 @@ */ public class CreateIndexSettings { private final IndexCoordinates indexCoordinates; - private final Set aliasCoordinates; + private final Set aliases; + @Nullable private final Map settings; @Nullable @@ -40,22 +41,22 @@ public class CreateIndexSettings { private CreateIndexSettings(Builder builder) { this.indexCoordinates = builder.indexCoordinates; - this.aliasCoordinates = builder.aliasCoordinates; + this.aliases = builder.aliases; this.settings = builder.settings; this.mapping = builder.mapping; } - public static Builder builder() { - return new Builder(); + public static Builder builder(IndexCoordinates indexCoordinates) { + return new Builder(indexCoordinates); } public IndexCoordinates getIndexCoordinates() { return indexCoordinates; } - public AliasCoordinates[] getAliasCoordinates() { - return aliasCoordinates.toArray(AliasCoordinates[]::new); + public Alias[] getAliases() { + return aliases.toArray(Alias[]::new); } public Map getSettings() { @@ -69,30 +70,29 @@ public Document getMapping() { public static class Builder { private IndexCoordinates indexCoordinates; - private Set aliasCoordinates = new HashSet<>(); + private final Set aliases = new HashSet<>(); + @Nullable private Map settings; @Nullable private Document mapping; - public Builder withIndexCoordinates(IndexCoordinates indexCoordinates) { + public Builder(IndexCoordinates indexCoordinates) { Assert.notNull(indexCoordinates, "indexCoordinates must not be null"); this.indexCoordinates = indexCoordinates; - - return this; } - public Builder withAliasCoordinates(AliasCoordinates aliasCoordinates) { - Assert.notNull(aliasCoordinates, "aliasCoordinates must not be null"); - this.aliasCoordinates.add(aliasCoordinates); + public Builder withAlias(Alias alias) { + Assert.notNull(alias, "alias must not be null"); + this.aliases.add(alias); return this; } - public Builder withAliases(Set aliases) { + public Builder withAliases(Set aliases) { Assert.notNull(aliases, "aliases must not be null"); - this.aliasCoordinates.addAll(aliases); + this.aliases.addAll(aliases); return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 5f8d66842..e9d3d0c7a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -43,7 +43,14 @@ public interface ElasticsearchPersistentEntity extends PersistentEntity { IndexCoordinates getIndexCoordinates(); - Set getAliases(); + + /** + * Retrieves the aliases associated with the current entity. + * + * @return Returns a set of aliases of the {@link PersistentEntity}. + * @since 5.4 + */ + Set getAliases(); short getShards(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index 819a101dc..4ff39850e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.elasticsearch.annotations.Alias; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Dynamic; import org.springframework.data.elasticsearch.annotations.Field; @@ -85,6 +84,7 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit private final ConcurrentHashMap routingExpressions = new ConcurrentHashMap<>(); private @Nullable String routing; private final ContextConfiguration contextConfiguration; + private final Set aliases = new HashSet<>(); private final ConcurrentHashMap indexNameExpressions = new ConcurrentHashMap<>(); private final Lazy indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext); @@ -117,6 +117,7 @@ public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation, this.dynamic = document.dynamic(); this.storeIdInSource = document.storeIdInSource(); this.storeVersionInSource = document.storeVersionInSource(); + buildAliases(); } else { this.dynamic = Dynamic.INHERIT; this.storeIdInSource = true; @@ -144,33 +145,7 @@ public IndexCoordinates getIndexCoordinates() { } @Override - public Set getAliases() { - final Set aliases = new HashSet<>(); - - if (document != null) { - for (Alias alias : document.aliases()) { - if (alias.value().isEmpty()) { - continue; - } - - Query query = null; - if (!alias.filter().value().isEmpty()) { - query = new StringQuery(alias.filter().value()); - } - - aliases.add( - AliasCoordinates.builder(alias.value()) - .withFilter(query) - .withIndexRouting(alias.indexRouting()) - .withSearchRouting(alias.searchRouting()) - .withRouting(alias.routing()) - .withHidden(alias.isHidden()) - .withWriteIndex(alias.isWriteIndex()) - .build() - ); - } - } - + public Set getAliases() { return aliases; } @@ -651,4 +626,36 @@ public boolean getWriteTypeHints() { public Dynamic dynamic() { return dynamic; } + + /** + * Building once the aliases for the current document. + */ + private void buildAliases() { + // Clear the existing aliases. + aliases.clear(); + + if (document != null) { + for (org.springframework.data.elasticsearch.annotations.Alias alias : document.aliases()) { + if (alias.value().isEmpty()) { + continue; + } + + Query query = null; + if (!alias.filter().value().isEmpty()) { + query = new StringQuery(alias.filter().value()); + } + + aliases.add( + Alias.builder(alias.value()) + .withFilter(query) + .withIndexRouting(alias.indexRouting()) + .withSearchRouting(alias.searchRouting()) + .withRouting(alias.routing()) + .withHidden(alias.isHidden()) + .withWriteIndex(alias.isWriteIndex()) + .build() + ); + } + } + } } From 54e74286e06bcbdf2f81b11a7acd02f627787b46 Mon Sep 17 00:00:00 2001 From: Youssef Aouichaoui Date: Wed, 22 May 2024 22:14:29 +0200 Subject: [PATCH 5/5] Add integration test for reactive template. Signed-off-by: Youssef Aouichaoui --- ...activeIndexOperationsIntegrationTests.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexOperationsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexOperationsIntegrationTests.java index b37ba3c53..3a56d505c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveIndexOperationsIntegrationTests.java @@ -17,12 +17,19 @@ import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; import static org.springframework.data.elasticsearch.core.IndexOperationsAdapter.*; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.springframework.data.elasticsearch.annotations.Alias; +import org.springframework.data.elasticsearch.annotations.Filter; +import org.springframework.data.elasticsearch.client.elc.Queries; +import org.springframework.data.elasticsearch.core.query.StringQuery; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.LocalDate; +import java.util.HashSet; import java.util.Set; import org.json.JSONException; @@ -346,6 +353,33 @@ void shouldGetAliasData() { .verifyComplete(); } + @Test + void shouldCreateIndexWithAliases() { + // Given + indexNameProvider.increment(); + String indexName = indexNameProvider.indexName(); + indexOperations = operations.indexOps(EntityWithAliases.class); + blocking(indexOperations).createWithMapping(); + + // When + + // Then + indexOperations.getAliasesForIndex(indexName) + .as(StepVerifier::create) + .assertNext(aliases -> { + AliasData result = aliases.values().stream().findFirst().orElse(new HashSet<>()).stream().findFirst().orElse(null); + + assertThat(result).isNotNull(); + assertThat(result.getAlias()).isEqualTo("first_alias"); + assertThat(result.getFilterQuery()).asInstanceOf(InstanceOfAssertFactories.type(StringQuery.class)) + .extracting(StringQuery::getSource) + .asString() + .contains(Queries.wrapperQuery(""" + {"bool" : {"must" : {"term" : {"type" : "abc"}}}} + """).query()); + }).verifyComplete(); + } + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 3, replicas = 2, refreshInterval = "4s") static class Entity { @@ -401,4 +435,31 @@ public void setId(@Nullable String id) { this.id = id; } } + + @Document(indexName = "#{@indexNameProvider.indexName()}", aliases = { + @Alias(value = "first_alias", filter =@Filter(""" + {"bool" : {"must" : {"term" : {"type" : "abc"}}}} + """)) + }) + private static class EntityWithAliases { + @Nullable private @Id String id; + @Field(type = Text) private String type; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } }