Skip to content

Commit b31268c

Browse files
olcbeanjavanna
authored andcommitted
Add Get Aliases API to the high-level REST client (#28799)
Given the weirdness of the response returned by the get alias API, we went for a client specific response, which allows us to hold the error message, exception and status returned as part of the response together with aliases. See #30536 . Relates to #27205
1 parent abc6751 commit b31268c

File tree

17 files changed

+1001
-48
lines changed

17 files changed

+1001
-48
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client;
21+
22+
import org.elasticsearch.ElasticsearchException;
23+
import org.elasticsearch.action.ActionResponse;
24+
import org.elasticsearch.cluster.metadata.AliasMetaData;
25+
import org.elasticsearch.common.xcontent.StatusToXContentObject;
26+
import org.elasticsearch.common.xcontent.ToXContent;
27+
import org.elasticsearch.common.xcontent.XContentBuilder;
28+
import org.elasticsearch.common.xcontent.XContentParser;
29+
import org.elasticsearch.common.xcontent.XContentParser.Token;
30+
import org.elasticsearch.rest.RestStatus;
31+
32+
import java.io.IOException;
33+
import java.util.Collections;
34+
import java.util.HashMap;
35+
import java.util.HashSet;
36+
import java.util.Map;
37+
import java.util.Set;
38+
39+
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
40+
41+
/**
42+
* Response obtained from the get aliases API.
43+
* The format is pretty horrible as it holds aliases, but at the same time errors can come back through the status and error fields.
44+
* Such errors are mostly 404 - NOT FOUND for aliases that were specified but not found. In such case the client won't throw exception
45+
* so it allows to retrieve the returned aliases, while at the same time checking if errors were returned.
46+
* There's also the case where an exception is returned, like for instance an {@link org.elasticsearch.index.IndexNotFoundException}.
47+
* We would usually throw such exception, but we configure the client to not throw for 404 to support the case above, hence we also not
48+
* throw in case an index is not found, although it is a hard error that doesn't come back with aliases.
49+
*/
50+
public class GetAliasesResponse extends ActionResponse implements StatusToXContentObject {
51+
52+
private final RestStatus status;
53+
private final String error;
54+
private final ElasticsearchException exception;
55+
56+
private final Map<String, Set<AliasMetaData>> aliases;
57+
58+
GetAliasesResponse(RestStatus status, String error, Map<String, Set<AliasMetaData>> aliases) {
59+
this.status = status;
60+
this.error = error;
61+
this.aliases = aliases;
62+
this.exception = null;
63+
}
64+
65+
private GetAliasesResponse(RestStatus status, ElasticsearchException exception) {
66+
this.status = status;
67+
this.error = null;
68+
this.aliases = Collections.emptyMap();
69+
this.exception = exception;
70+
}
71+
72+
@Override
73+
public RestStatus status() {
74+
return status;
75+
}
76+
77+
/**
78+
* Return the possibly returned error, null otherwise
79+
*/
80+
public String getError() {
81+
return error;
82+
}
83+
84+
/**
85+
* Return the exception that may have been returned
86+
*/
87+
public ElasticsearchException getException() {
88+
return exception;
89+
}
90+
91+
/**
92+
* Return the requested aliases
93+
*/
94+
public Map<String, Set<AliasMetaData>> getAliases() {
95+
return aliases;
96+
}
97+
98+
@Override
99+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
100+
builder.startObject();
101+
{
102+
if (status != RestStatus.OK) {
103+
builder.field("error", error);
104+
builder.field("status", status.getStatus());
105+
}
106+
107+
for (Map.Entry<String, Set<AliasMetaData>> entry : aliases.entrySet()) {
108+
builder.startObject(entry.getKey());
109+
{
110+
builder.startObject("aliases");
111+
{
112+
for (final AliasMetaData alias : entry.getValue()) {
113+
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
114+
}
115+
}
116+
builder.endObject();
117+
}
118+
builder.endObject();
119+
}
120+
}
121+
builder.endObject();
122+
return builder;
123+
}
124+
125+
/**
126+
* Parse the get aliases response
127+
*/
128+
public static GetAliasesResponse fromXContent(XContentParser parser) throws IOException {
129+
if (parser.currentToken() == null) {
130+
parser.nextToken();
131+
}
132+
ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
133+
Map<String, Set<AliasMetaData>> aliases = new HashMap<>();
134+
135+
String currentFieldName;
136+
Token token;
137+
String error = null;
138+
ElasticsearchException exception = null;
139+
RestStatus status = RestStatus.OK;
140+
141+
while (parser.nextToken() != Token.END_OBJECT) {
142+
if (parser.currentToken() == Token.FIELD_NAME) {
143+
currentFieldName = parser.currentName();
144+
145+
if ("status".equals(currentFieldName)) {
146+
if ((token = parser.nextToken()) != Token.FIELD_NAME) {
147+
ensureExpectedToken(Token.VALUE_NUMBER, token, parser::getTokenLocation);
148+
status = RestStatus.fromCode(parser.intValue());
149+
}
150+
} else if ("error".equals(currentFieldName)) {
151+
token = parser.nextToken();
152+
if (token == Token.VALUE_STRING) {
153+
error = parser.text();
154+
} else if (token == Token.START_OBJECT) {
155+
parser.nextToken();
156+
exception = ElasticsearchException.innerFromXContent(parser, true);
157+
} else if (token == Token.START_ARRAY) {
158+
parser.skipChildren();
159+
}
160+
} else {
161+
String indexName = parser.currentName();
162+
if (parser.nextToken() == Token.START_OBJECT) {
163+
Set<AliasMetaData> parseInside = parseAliases(parser);
164+
aliases.put(indexName, parseInside);
165+
}
166+
}
167+
}
168+
}
169+
if (exception != null) {
170+
assert error == null;
171+
assert aliases.isEmpty();
172+
return new GetAliasesResponse(status, exception);
173+
}
174+
return new GetAliasesResponse(status, error, aliases);
175+
}
176+
177+
private static Set<AliasMetaData> parseAliases(XContentParser parser) throws IOException {
178+
Set<AliasMetaData> aliases = new HashSet<>();
179+
Token token;
180+
String currentFieldName = null;
181+
while ((token = parser.nextToken()) != Token.END_OBJECT) {
182+
if (token == Token.FIELD_NAME) {
183+
currentFieldName = parser.currentName();
184+
} else if (token == Token.START_OBJECT) {
185+
if ("aliases".equals(currentFieldName)) {
186+
while (parser.nextToken() != Token.END_OBJECT) {
187+
AliasMetaData fromXContent = AliasMetaData.Builder.fromXContent(parser);
188+
aliases.add(fromXContent);
189+
}
190+
} else {
191+
parser.skipChildren();
192+
}
193+
} else if (token == Token.START_ARRAY) {
194+
parser.skipChildren();
195+
}
196+
}
197+
return aliases;
198+
}
199+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@
5858
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
5959
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
6060
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
61+
import org.elasticsearch.rest.RestStatus;
6162

6263
import java.io.IOException;
6364
import java.util.Collections;
6465

6566
import static java.util.Collections.emptySet;
67+
import static java.util.Collections.singleton;
6668

6769
/**
6870
* A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Indices API.
@@ -978,6 +980,33 @@ public void rolloverAsync(RolloverRequest rolloverRequest, ActionListener<Rollov
978980
listener, emptySet(), headers);
979981
}
980982

983+
/**
984+
* Gets one or more aliases using the Get Index Aliases API.
985+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
986+
* elastic.co</a>
987+
* @param getAliasesRequest the request
988+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
989+
* @return the response
990+
* @throws IOException in case there is a problem sending the request or parsing back the response
991+
*/
992+
public GetAliasesResponse getAlias(GetAliasesRequest getAliasesRequest, RequestOptions options) throws IOException {
993+
return restHighLevelClient.performRequestAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
994+
GetAliasesResponse::fromXContent, singleton(RestStatus.NOT_FOUND.getStatus()));
995+
}
996+
997+
/**
998+
* Asynchronously gets one or more aliases using the Get Index Aliases API.
999+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
1000+
* elastic.co</a>
1001+
* @param getAliasesRequest the request
1002+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1003+
* @param listener the listener to be notified upon request completion
1004+
*/
1005+
public void getAliasAsync(GetAliasesRequest getAliasesRequest, RequestOptions options, ActionListener<GetAliasesResponse> listener) {
1006+
restHighLevelClient.performRequestAsyncAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
1007+
GetAliasesResponse::fromXContent, listener, singleton(RestStatus.NOT_FOUND.getStatus()));
1008+
}
1009+
9811010
/**
9821011
* Updates specific index level settings using the Update Indices Settings API.
9831012
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html"> Update Indices Settings

client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,17 @@ static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) t
852852
return request;
853853
}
854854

855+
static Request getAlias(GetAliasesRequest getAliasesRequest) {
856+
String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
857+
String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
858+
String endpoint = endpoint(indices, "_alias", aliases);
859+
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
860+
Params params = new Params(request);
861+
params.withIndicesOptions(getAliasesRequest.indicesOptions());
862+
params.withLocal(getAliasesRequest.local());
863+
return request;
864+
}
865+
855866
private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
856867
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
857868
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,10 +1021,10 @@ protected final <Req extends ActionRequest, Resp> Resp performRequest(Req reques
10211021
try {
10221022
return responseConverter.apply(e.getResponse());
10231023
} catch (Exception innerException) {
1024-
//the exception is ignored as we now try to parse the response as an error.
1025-
//this covers cases like get where 404 can either be a valid document not found response,
1026-
//or an error for which parsing is completely different. We try to consider the 404 response as a valid one
1027-
//first. If parsing of the response breaks, we fall back to parsing it as an error.
1024+
// the exception is ignored as we now try to parse the response as an error.
1025+
// this covers cases like get where 404 can either be a valid document not found response,
1026+
// or an error for which parsing is completely different. We try to consider the 404 response as a valid one
1027+
// first. If parsing of the response breaks, we fall back to parsing it as an error.
10281028
throw parseResponseException(e);
10291029
}
10301030
}
@@ -1109,10 +1109,10 @@ public void onFailure(Exception exception) {
11091109
try {
11101110
actionListener.onResponse(responseConverter.apply(response));
11111111
} catch (Exception innerException) {
1112-
//the exception is ignored as we now try to parse the response as an error.
1113-
//this covers cases like get where 404 can either be a valid document not found response,
1114-
//or an error for which parsing is completely different. We try to consider the 404 response as a valid one
1115-
//first. If parsing of the response breaks, we fall back to parsing it as an error.
1112+
// the exception is ignored as we now try to parse the response as an error.
1113+
// this covers cases like get where 404 can either be a valid document not found response,
1114+
// or an error for which parsing is completely different. We try to consider the 404 response as a valid one
1115+
// first. If parsing of the response breaks, we fall back to parsing it as an error.
11161116
actionListener.onFailure(parseResponseException(responseException));
11171117
}
11181118
} else {

0 commit comments

Comments
 (0)