Skip to content

Commit fbe54e4

Browse files
authored
Add support for index aliases.
Original Pull Request #2905 Closes #2599
1 parent 86e0e66 commit fbe54e4

File tree

13 files changed

+743
-16
lines changed

13 files changed

+743
-16
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
19+
import org.springframework.core.annotation.AliasFor;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Inherited;
23+
import java.lang.annotation.Repeatable;
24+
import java.lang.annotation.Retention;
25+
import java.lang.annotation.RetentionPolicy;
26+
import java.lang.annotation.Target;
27+
28+
/**
29+
* Identifies an alias for the index.
30+
*
31+
* @author Youssef Aouichaoui
32+
* @since 5.4
33+
*/
34+
@Inherited
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target({ElementType.TYPE})
37+
@Repeatable(Aliases.class)
38+
public @interface Alias {
39+
/**
40+
* @return Index alias name. Alias for {@link #alias}.
41+
*/
42+
@AliasFor("alias")
43+
String value() default "";
44+
45+
/**
46+
* @return Index alias name. Alias for {@link #value}.
47+
*/
48+
@AliasFor("value")
49+
String alias() default "";
50+
51+
/**
52+
* @return Query used to limit documents the alias can access.
53+
*/
54+
Filter filter() default @Filter;
55+
56+
/**
57+
* @return Used to route indexing operations to a specific shard.
58+
*/
59+
String indexRouting() default "";
60+
61+
/**
62+
* @return Used to route indexing and search operations to a specific shard.
63+
*/
64+
String routing() default "";
65+
66+
/**
67+
* @return Used to route search operations to a specific shard.
68+
*/
69+
String searchRouting() default "";
70+
71+
/**
72+
* @return Is the alias hidden?
73+
*/
74+
boolean isHidden() default false;
75+
76+
/**
77+
* @return Is it the 'write index' for the alias?
78+
*/
79+
boolean isWriteIndex() default false;
80+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Inherited;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Container annotation that aggregates several {@link Alias} annotations.
27+
*
28+
* @author Youssef Aouichaoui
29+
* @see Alias
30+
* @since 5.4
31+
*/
32+
@Inherited
33+
@Retention(RetentionPolicy.RUNTIME)
34+
@Target({ElementType.TYPE})
35+
public @interface Aliases {
36+
Alias[] value();
37+
}

src/main/java/org/springframework/data/elasticsearch/annotations/Document.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@
100100
*/
101101
boolean storeVersionInSource() default true;
102102

103+
/**
104+
* Aliases for the index.
105+
*
106+
* @since 5.4
107+
*/
108+
Alias[] aliases() default {};
109+
103110
/**
104111
* @since 4.3
105112
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
19+
import org.springframework.core.annotation.AliasFor;
20+
21+
/**
22+
* Query used to limit documents.
23+
*
24+
* @author Youssef Aouichaoui
25+
* @since 5.4
26+
*/
27+
public @interface Filter {
28+
/**
29+
* @return Query used to limit documents. Alias for {@link #query}.
30+
*/
31+
@AliasFor("query")
32+
String value() default "";
33+
34+
/**
35+
* @return Query used to limit documents. Alias for {@link #value}.
36+
*/
37+
@AliasFor("value")
38+
String query() default "";
39+
}

src/main/java/org/springframework/data/elasticsearch/client/elc/IndicesTemplate.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import co.elastic.clients.transport.ElasticsearchTransport;
2222
import co.elastic.clients.transport.endpoints.BooleanResponse;
2323

24+
import java.util.HashSet;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.Objects;
@@ -46,6 +47,8 @@
4647
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
4748
import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest;
4849
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
50+
import org.springframework.data.elasticsearch.core.mapping.Alias;
51+
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
4952
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
5053
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
5154
import org.springframework.lang.Nullable;
@@ -137,11 +140,14 @@ public boolean createWithMapping() {
137140

138141
protected boolean doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
139142
@Nullable Document mapping) {
140-
141-
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
142-
Assert.notNull(settings, "settings must not be null");
143-
144-
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
143+
Set<Alias> aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>();
144+
CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates)
145+
.withAliases(aliases)
146+
.withSettings(settings)
147+
.withMapping(mapping)
148+
.build();
149+
150+
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
145151
CreateIndexResponse createIndexResponse = execute(client -> client.create(createIndexRequest));
146152
return Boolean.TRUE.equals(createIndexResponse.acknowledged());
147153
}
@@ -449,5 +455,14 @@ public IndexCoordinates getIndexCoordinates() {
449455
public IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
450456
return getRequiredPersistentEntity(clazz).getIndexCoordinates();
451457
}
458+
459+
/**
460+
* Get the {@link Alias} of the provided class.
461+
*
462+
* @param clazz provided class that can be used to extract aliases.
463+
*/
464+
public Set<Alias> getAliasesFor(Class<?> clazz) {
465+
return getRequiredPersistentEntity(clazz).getAliases();
466+
}
452467
// endregion
453468
}

src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveIndicesTemplate.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
import co.elastic.clients.elasticsearch.indices.*;
2222
import co.elastic.clients.transport.ElasticsearchTransport;
2323
import co.elastic.clients.transport.endpoints.BooleanResponse;
24+
import org.springframework.data.elasticsearch.core.mapping.Alias;
25+
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
2426
import reactor.core.publisher.Flux;
2527
import reactor.core.publisher.Mono;
2628

29+
import java.util.HashSet;
2730
import java.util.Map;
2831
import java.util.Objects;
2932
import java.util.Set;
@@ -130,8 +133,14 @@ public Mono<Boolean> createWithMapping() {
130133

131134
private Mono<Boolean> doCreate(IndexCoordinates indexCoordinates, Map<String, Object> settings,
132135
@Nullable Document mapping) {
133-
134-
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexCoordinates, settings, mapping);
136+
Set<Alias> aliases = (boundClass != null) ? getAliasesFor(boundClass) : new HashSet<>();
137+
CreateIndexSettings indexSettings = CreateIndexSettings.builder(indexCoordinates)
138+
.withAliases(aliases)
139+
.withSettings(settings)
140+
.withMapping(mapping)
141+
.build();
142+
143+
CreateIndexRequest createIndexRequest = requestConverter.indicesCreateRequest(indexSettings);
135144
Mono<CreateIndexResponse> createIndexResponse = Mono.from(execute(client -> client.create(createIndexRequest)));
136145
return createIndexResponse.map(CreateIndexResponse::acknowledged);
137146
}
@@ -435,6 +444,15 @@ private IndexCoordinates getIndexCoordinatesFor(Class<?> clazz) {
435444
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getIndexCoordinates();
436445
}
437446

447+
/**
448+
* Get the {@link Alias} of the provided class.
449+
*
450+
* @param clazz provided class that can be used to extract aliases.
451+
*/
452+
private Set<Alias> getAliasesFor(Class<?> clazz) {
453+
return elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(clazz).getAliases();
454+
}
455+
438456
private Class<?> checkForBoundClass() {
439457
if (boundClass == null) {
440458
throw new InvalidDataAccessApiUsageException("IndexOperations are not bound");

src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
import org.springframework.data.elasticsearch.core.index.GetTemplateRequest;
8989
import org.springframework.data.elasticsearch.core.index.PutIndexTemplateRequest;
9090
import org.springframework.data.elasticsearch.core.index.PutTemplateRequest;
91+
import org.springframework.data.elasticsearch.core.mapping.Alias;
92+
import org.springframework.data.elasticsearch.core.mapping.CreateIndexSettings;
9193
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
9294
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
9395
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
@@ -170,7 +172,7 @@ public co.elastic.clients.elasticsearch.cluster.PutComponentTemplateRequest clus
170172
}));
171173
}
172174

173-
private Alias.Builder buildAlias(AliasActionParameters parameters, Alias.Builder aliasBuilder) {
175+
private co.elastic.clients.elasticsearch.indices.Alias.Builder buildAlias(AliasActionParameters parameters, co.elastic.clients.elasticsearch.indices.Alias.Builder aliasBuilder) {
174176

175177
if (parameters.getRouting() != null) {
176178
aliasBuilder.routing(parameters.getRouting());
@@ -234,17 +236,25 @@ public ExistsRequest indicesExistsRequest(IndexCoordinates indexCoordinates) {
234236
return new ExistsRequest.Builder().index(Arrays.asList(indexCoordinates.getIndexNames())).build();
235237
}
236238

237-
public CreateIndexRequest indicesCreateRequest(IndexCoordinates indexCoordinates, Map<String, Object> settings,
238-
@Nullable Document mapping) {
239-
240-
Assert.notNull(indexCoordinates, "indexCoordinates must not be null");
241-
Assert.notNull(settings, "settings must not be null");
239+
public CreateIndexRequest indicesCreateRequest(CreateIndexSettings indexSettings) {
240+
Map<String, co.elastic.clients.elasticsearch.indices.Alias> aliases = new HashMap<>();
241+
for (Alias alias : indexSettings.getAliases()) {
242+
co.elastic.clients.elasticsearch.indices.Alias esAlias = co.elastic.clients.elasticsearch.indices.Alias.of(ab -> ab.filter(getQuery(alias.getFilter(), null))
243+
.routing(alias.getRouting())
244+
.indexRouting(alias.getIndexRouting())
245+
.searchRouting(alias.getSearchRouting())
246+
.isHidden(alias.getHidden())
247+
.isWriteIndex(alias.getWriteIndex())
248+
);
249+
aliases.put(alias.getAlias(), esAlias);
250+
}
242251

243252
// note: the new client does not support the index.storeType anymore
244253
return new CreateIndexRequest.Builder() //
245-
.index(indexCoordinates.getIndexName()) //
246-
.settings(indexSettings(settings)) //
247-
.mappings(typeMapping(mapping)) //
254+
.index(indexSettings.getIndexCoordinates().getIndexName()) //
255+
.aliases(aliases)
256+
.settings(indexSettings(indexSettings.getSettings())) //
257+
.mappings(typeMapping(indexSettings.getMapping())) //
248258
.build();
249259
}
250260

0 commit comments

Comments
 (0)