Browse Source

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
olcbean 7 years ago
parent
commit
7d7ead95b2
17 changed files with 1001 additions and 48 deletions
  1. 199 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/GetAliasesResponse.java
  2. 29 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java
  3. 11 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java
  4. 8 8
      client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java
  5. 175 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java
  6. 213 20
      client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
  7. 31 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java
  8. 74 2
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java
  9. 94 0
      docs/java-rest/high-level/indices/get_alias.asciidoc
  10. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc
  11. 1 1
      server/src/main/java/org/elasticsearch/ElasticsearchException.java
  12. 18 1
      server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesResponse.java
  13. 0 1
      server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/TransportGetAliasesAction.java
  14. 0 1
      server/src/main/java/org/elasticsearch/cluster/metadata/AliasMetaData.java
  15. 6 13
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java
  16. 139 0
      server/src/test/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesResponseTests.java
  17. 1 1
      test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java

+ 199 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/GetAliasesResponse.java

@@ -0,0 +1,199 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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
+ *
+ *    http://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.elasticsearch.client;
+
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.cluster.metadata.AliasMetaData;
+import org.elasticsearch.common.xcontent.StatusToXContentObject;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
+import org.elasticsearch.rest.RestStatus;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+/**
+ * Response obtained from the get aliases API.
+ * The format is pretty horrible as it holds aliases, but at the same time errors can come back through the status and error fields.
+ * Such errors are mostly 404 - NOT FOUND for aliases that were specified but not found. In such case the client won't throw exception
+ * so it allows to retrieve the returned aliases, while at the same time checking if errors were returned.
+ * There's also the case where an exception is returned, like for instance an {@link org.elasticsearch.index.IndexNotFoundException}.
+ * 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
+ * throw in case an index is not found, although it is a hard error that doesn't come back with aliases.
+ */
+public class GetAliasesResponse extends ActionResponse implements StatusToXContentObject {
+
+    private final RestStatus status;
+    private final String error;
+    private final ElasticsearchException exception;
+
+    private final Map<String, Set<AliasMetaData>> aliases;
+
+    GetAliasesResponse(RestStatus status, String error, Map<String, Set<AliasMetaData>> aliases) {
+        this.status = status;
+        this.error = error;
+        this.aliases = aliases;
+        this.exception = null;
+    }
+
+    private GetAliasesResponse(RestStatus status, ElasticsearchException exception) {
+        this.status = status;
+        this.error = null;
+        this.aliases = Collections.emptyMap();
+        this.exception = exception;
+    }
+
+    @Override
+    public RestStatus status() {
+        return status;
+    }
+
+    /**
+     * Return the possibly returned error, null otherwise
+     */
+    public String getError() {
+        return error;
+    }
+
+    /**
+     * Return the exception that may have been returned
+     */
+    public ElasticsearchException getException() {
+        return exception;
+    }
+
+    /**
+     * Return the requested aliases
+     */
+    public Map<String, Set<AliasMetaData>> getAliases() {
+        return aliases;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        {
+            if (status != RestStatus.OK) {
+                builder.field("error", error);
+                builder.field("status", status.getStatus());
+            }
+
+            for (Map.Entry<String, Set<AliasMetaData>> entry : aliases.entrySet()) {
+                builder.startObject(entry.getKey());
+                {
+                    builder.startObject("aliases");
+                    {
+                        for (final AliasMetaData alias : entry.getValue()) {
+                            AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
+                        }
+                    }
+                    builder.endObject();
+                }
+                builder.endObject();
+            }
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    /**
+     * Parse the get aliases response
+     */
+    public static GetAliasesResponse fromXContent(XContentParser parser) throws IOException {
+        if (parser.currentToken() == null) {
+            parser.nextToken();
+        }
+        ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
+        Map<String, Set<AliasMetaData>> aliases = new HashMap<>();
+
+        String currentFieldName;
+        Token token;
+        String error = null;
+        ElasticsearchException exception = null;
+        RestStatus status = RestStatus.OK;
+
+        while (parser.nextToken() != Token.END_OBJECT) {
+            if (parser.currentToken() == Token.FIELD_NAME) {
+                currentFieldName = parser.currentName();
+
+                if ("status".equals(currentFieldName)) {
+                    if ((token = parser.nextToken()) != Token.FIELD_NAME) {
+                        ensureExpectedToken(Token.VALUE_NUMBER, token, parser::getTokenLocation);
+                        status = RestStatus.fromCode(parser.intValue());
+                    }
+                } else if ("error".equals(currentFieldName)) {
+                    token = parser.nextToken();
+                    if (token == Token.VALUE_STRING) {
+                        error = parser.text();
+                    } else if (token == Token.START_OBJECT) {
+                        parser.nextToken();
+                        exception = ElasticsearchException.innerFromXContent(parser, true);
+                    } else if (token == Token.START_ARRAY) {
+                        parser.skipChildren();
+                    }
+                } else {
+                    String indexName = parser.currentName();
+                    if (parser.nextToken() == Token.START_OBJECT) {
+                        Set<AliasMetaData> parseInside = parseAliases(parser);
+                        aliases.put(indexName, parseInside);
+                    }
+                }
+            }
+        }
+        if (exception != null) {
+            assert error == null;
+            assert aliases.isEmpty();
+            return new GetAliasesResponse(status, exception);
+        }
+        return new GetAliasesResponse(status, error, aliases);
+    }
+
+    private static Set<AliasMetaData> parseAliases(XContentParser parser) throws IOException {
+        Set<AliasMetaData> aliases = new HashSet<>();
+        Token token;
+        String currentFieldName = null;
+        while ((token = parser.nextToken()) != Token.END_OBJECT) {
+            if (token == Token.FIELD_NAME) {
+                currentFieldName = parser.currentName();
+            } else if (token == Token.START_OBJECT) {
+                if ("aliases".equals(currentFieldName)) {
+                    while (parser.nextToken() != Token.END_OBJECT) {
+                        AliasMetaData fromXContent = AliasMetaData.Builder.fromXContent(parser);
+                        aliases.add(fromXContent);
+                    }
+                } else {
+                    parser.skipChildren();
+                }
+            } else if (token == Token.START_ARRAY) {
+                parser.skipChildren();
+            }
+        }
+        return aliases;
+    }
+}

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

@@ -58,11 +58,13 @@ import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequ
 import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
 import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
 import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
+import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
 import java.util.Collections;
 
 import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
 
 /**
  * A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Indices API.
@@ -978,6 +980,33 @@ public final class IndicesClient {
                 listener, emptySet(), headers);
     }
 
+    /**
+     * Gets one or more aliases using the Get Index Aliases API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
+     * elastic.co</a>
+     * @param getAliasesRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetAliasesResponse getAlias(GetAliasesRequest getAliasesRequest, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
+                GetAliasesResponse::fromXContent, singleton(RestStatus.NOT_FOUND.getStatus()));
+    }
+
+    /**
+     * Asynchronously gets one or more aliases using the Get Index Aliases API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
+     * elastic.co</a>
+     * @param getAliasesRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     */
+    public void getAliasAsync(GetAliasesRequest getAliasesRequest, RequestOptions options, ActionListener<GetAliasesResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
+                GetAliasesResponse::fromXContent, listener, singleton(RestStatus.NOT_FOUND.getStatus()));
+    }
+
     /**
      * Updates specific index level settings using the Update Indices Settings API.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html"> Update Indices Settings

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

@@ -831,6 +831,17 @@ final class RequestConverters {
         return request;
     }
 
+    static Request getAlias(GetAliasesRequest getAliasesRequest) {
+        String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
+        String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
+        String endpoint = endpoint(indices, "_alias", aliases);
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+        Params params = new Params(request);
+        params.withIndicesOptions(getAliasesRequest.indicesOptions());
+        params.withLocal(getAliasesRequest.local());
+        return request;
+    }
+
     static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) throws IOException {
         String[] names = getIndexTemplatesRequest.names();
         String endpoint = new EndpointBuilder().addPathPartAsIs("_template").addCommaSeparatedPathParts(names).build();

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

@@ -1021,10 +1021,10 @@ public class RestHighLevelClient implements Closeable {
                 try {
                     return responseConverter.apply(e.getResponse());
                 } catch (Exception innerException) {
-                    //the exception is ignored as we now try to parse the response as an error.
-                    //this covers cases like get where 404 can either be a valid document not found response,
-                    //or an error for which parsing is completely different. We try to consider the 404 response as a valid one
-                    //first. If parsing of the response breaks, we fall back to parsing it as an error.
+                    // the exception is ignored as we now try to parse the response as an error.
+                    // this covers cases like get where 404 can either be a valid document not found response,
+                    // or an error for which parsing is completely different. We try to consider the 404 response as a valid one
+                    // first. If parsing of the response breaks, we fall back to parsing it as an error.
                     throw parseResponseException(e);
                 }
             }
@@ -1109,10 +1109,10 @@ public class RestHighLevelClient implements Closeable {
                         try {
                             actionListener.onResponse(responseConverter.apply(response));
                         } catch (Exception innerException) {
-                            //the exception is ignored as we now try to parse the response as an error.
-                            //this covers cases like get where 404 can either be a valid document not found response,
-                            //or an error for which parsing is completely different. We try to consider the 404 response as a valid one
-                            //first. If parsing of the response breaks, we fall back to parsing it as an error.
+                            // the exception is ignored as we now try to parse the response as an error.
+                            // this covers cases like get where 404 can either be a valid document not found response,
+                            // or an error for which parsing is completely different. We try to consider the 404 response as a valid one
+                            // first. If parsing of the response breaks, we fall back to parsing it as an error.
                             actionListener.onFailure(parseResponseException(responseException));
                         }
                     } else {

+ 175 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/GetAliasesResponseTests.java

@@ -0,0 +1,175 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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
+ *
+ *    http://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.elasticsearch.client;
+
+import org.elasticsearch.cluster.metadata.AliasMetaData;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+
+public class GetAliasesResponseTests extends AbstractXContentTestCase<GetAliasesResponse> {
+
+    @Override
+    protected GetAliasesResponse createTestInstance() {
+        RestStatus status = randomFrom(RestStatus.OK, RestStatus.NOT_FOUND);
+        String errorMessage = RestStatus.OK == status ? null : randomAlphaOfLengthBetween(5, 10);
+        return new GetAliasesResponse(status, errorMessage, createIndicesAliasesMap(0, 5));
+    }
+
+    private static Map<String, Set<AliasMetaData>> createIndicesAliasesMap(int min, int max) {
+        Map<String, Set<AliasMetaData>> map = new HashMap<>();
+        int indicesNum = randomIntBetween(min, max);
+        for (int i = 0; i < indicesNum; i++) {
+            String index = randomAlphaOfLength(5);
+            Set<AliasMetaData> aliasMetaData = new HashSet<>();
+            int aliasesNum = randomIntBetween(0, 3);
+            for (int alias = 0; alias < aliasesNum; alias++) {
+                aliasMetaData.add(createAliasMetaData());
+            }
+            map.put(index, aliasMetaData);
+        }
+        return map;
+    }
+
+    private static AliasMetaData createAliasMetaData() {
+        AliasMetaData.Builder builder = AliasMetaData.builder(randomAlphaOfLengthBetween(3, 10));
+        if (randomBoolean()) {
+            builder.routing(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.searchRouting(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.indexRouting(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.filter("{\"term\":{\"year\":2016}}");
+        }
+        return builder.build();
+    }
+
+    @Override
+    protected GetAliasesResponse doParseInstance(XContentParser parser) throws IOException {
+        return GetAliasesResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected Predicate<String> getRandomFieldsExcludeFilter() {
+        return p -> p.equals("") // do not add elements at the top-level as any element at this level is parsed as a new index
+                || p.endsWith(".aliases") // do not add new alias
+                || p.contains(".filter"); // do not insert random data into AliasMetaData#filter
+    }
+
+    @Override
+    protected boolean supportsUnknownFields() {
+        return true;
+    }
+
+    @Override
+    protected void assertEqualInstances(GetAliasesResponse expectedInstance, GetAliasesResponse newInstance) {
+        assertEquals(expectedInstance.getAliases(), newInstance.getAliases());
+        assertEquals(expectedInstance.status(), newInstance.status());
+        assertEquals(expectedInstance.getError(), newInstance.getError());
+        assertNull(expectedInstance.getException());
+        assertNull(newInstance.getException());
+    }
+
+    public void testFromXContentWithElasticsearchException() throws IOException {
+        String xContent =
+                "{" +
+                "  \"error\": {" +
+                "    \"root_cause\": [" +
+                "      {" +
+                "        \"type\": \"index_not_found_exception\"," +
+                "        \"reason\": \"no such index\"," +
+                "        \"resource.type\": \"index_or_alias\"," +
+                "        \"resource.id\": \"index\"," +
+                "        \"index_uuid\": \"_na_\"," +
+                "        \"index\": \"index\"" +
+                "      }" +
+                "    ]," +
+                "    \"type\": \"index_not_found_exception\"," +
+                "    \"reason\": \"no such index\"," +
+                "    \"resource.type\": \"index_or_alias\"," +
+                "    \"resource.id\": \"index\"," +
+                "    \"index_uuid\": \"_na_\"," +
+                "    \"index\": \"index\"" +
+                "  }," +
+                "  \"status\": 404" +
+                "}";
+
+        try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
+            GetAliasesResponse getAliasesResponse = GetAliasesResponse.fromXContent(parser);
+            assertThat(getAliasesResponse.getError(), nullValue());
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getException().getMessage(),
+                    equalTo("Elasticsearch exception [type=index_not_found_exception, reason=no such index]"));
+        }
+    }
+
+    public void testFromXContentWithNoAliasFound() throws IOException {
+        String xContent =
+                "{" +
+                        "  \"error\": \"alias [aa] missing\"," +
+                        "  \"status\": 404" +
+                        "}";
+        try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
+            GetAliasesResponse getAliasesResponse = GetAliasesResponse.fromXContent(parser);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getError(), equalTo("alias [aa] missing"));
+            assertThat(getAliasesResponse.getException(), nullValue());
+        }
+    }
+
+    public void testFromXContentWithMissingAndFoundAlias() throws IOException {
+        String xContent =
+                "{" +
+                        "  \"error\": \"alias [something] missing\"," +
+                        "  \"status\": 404," +
+                        "  \"index\": {" +
+                        "    \"aliases\": {" +
+                        "      \"alias\": {}" +
+                        "    }" +
+                        "  }" +
+                        "}";
+        final String index = "index";
+        try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
+            GetAliasesResponse response = GetAliasesResponse.fromXContent(parser);
+            assertThat(response.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(response.getError(), equalTo("alias [something] missing"));
+            assertThat(response.getAliases().size(), equalTo(1));
+            assertThat(response.getAliases().get(index).size(), equalTo(1));
+            AliasMetaData aliasMetaData = response.getAliases().get(index).iterator().next();
+            assertThat(aliasMetaData.alias(), equalTo("alias"));
+            assertThat(response.getException(), nullValue());
+        }
+    }
+}

+ 213 - 20
client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java

@@ -52,10 +52,10 @@ import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
 import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
 import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
 import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
-import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
-import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
+import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
+import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
@@ -67,6 +67,7 @@ import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.action.support.broadcast.BroadcastResponse;
+import org.elasticsearch.cluster.metadata.AliasMetaData;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
 import org.elasticsearch.common.ValidationException;
@@ -98,6 +99,8 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.startsWith;
 
 public class IndicesClientIT extends ESRestHighLevelClientTestCase {
@@ -320,28 +323,27 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
         assertEquals(0, getSettingsResponse.getIndexToSettings().get("get_settings_index").size());
         assertEquals(1, getSettingsResponse.getIndexToDefaultSettings().get("get_settings_index").size());
     }
+
     public void testPutMapping() throws IOException {
-        {
-            // Add mappings to index
-            String indexName = "mapping_index";
-            createIndex(indexName, Settings.EMPTY);
+        // Add mappings to index
+        String indexName = "mapping_index";
+        createIndex(indexName, Settings.EMPTY);
 
-            PutMappingRequest putMappingRequest = new PutMappingRequest(indexName);
-            putMappingRequest.type("type_name");
-            XContentBuilder mappingBuilder = JsonXContent.contentBuilder();
-            mappingBuilder.startObject().startObject("properties").startObject("field");
-            mappingBuilder.field("type", "text");
-            mappingBuilder.endObject().endObject().endObject();
-            putMappingRequest.source(mappingBuilder);
+        PutMappingRequest putMappingRequest = new PutMappingRequest(indexName);
+        putMappingRequest.type("type_name");
+        XContentBuilder mappingBuilder = JsonXContent.contentBuilder();
+        mappingBuilder.startObject().startObject("properties").startObject("field");
+        mappingBuilder.field("type", "text");
+        mappingBuilder.endObject().endObject().endObject();
+        putMappingRequest.source(mappingBuilder);
 
-            PutMappingResponse putMappingResponse =
-                    execute(putMappingRequest, highLevelClient().indices()::putMapping, highLevelClient().indices()::putMappingAsync,
-                            highLevelClient().indices()::putMapping, highLevelClient().indices()::putMappingAsync);
-            assertTrue(putMappingResponse.isAcknowledged());
+        PutMappingResponse putMappingResponse =
+                execute(putMappingRequest, highLevelClient().indices()::putMapping, highLevelClient().indices()::putMappingAsync,
+                        highLevelClient().indices()::putMapping, highLevelClient().indices()::putMappingAsync);
+        assertTrue(putMappingResponse.isAcknowledged());
 
-            Map<String, Object> getIndexResponse = getAsMap(indexName);
-            assertEquals("text", XContentMapValues.extractValue(indexName + ".mappings.type_name.properties.field.type", getIndexResponse));
-        }
+        Map<String, Object> getIndexResponse = getAsMap(indexName);
+        assertEquals("text", XContentMapValues.extractValue(indexName + ".mappings.type_name.properties.field.type", getIndexResponse));
     }
 
     public void testGetMapping() throws IOException {
@@ -853,6 +855,197 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetAlias() throws IOException {
+        {
+            createIndex("index1", Settings.EMPTY);
+            client().performRequest(HttpPut.METHOD_NAME, "/index1/_alias/alias1");
+
+            createIndex("index2", Settings.EMPTY);
+            client().performRequest(HttpPut.METHOD_NAME, "/index2/_alias/alias2");
+
+            createIndex("index3", Settings.EMPTY);
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().aliases("alias1");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData, notNullValue());
+            assertThat(aliasMetaData.alias(), equalTo("alias1"));
+            assertThat(aliasMetaData.getFilter(), nullValue());
+            assertThat(aliasMetaData.getIndexRouting(), nullValue());
+            assertThat(aliasMetaData.getSearchRouting(), nullValue());
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().aliases("alias*");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(2));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().aliases("_all");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(2));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().aliases("*");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(2));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices("_all");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(3));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+            assertThat(getAliasesResponse.getAliases().get("index3").size(), equalTo(0));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices("ind*");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(3));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+            assertThat(getAliasesResponse.getAliases().get("index3").size(), equalTo(0));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(3));
+            assertThat(getAliasesResponse.getAliases().get("index1").size(), equalTo(1));
+            AliasMetaData aliasMetaData1 = getAliasesResponse.getAliases().get("index1").iterator().next();
+            assertThat(aliasMetaData1, notNullValue());
+            assertThat(aliasMetaData1.alias(), equalTo("alias1"));
+            assertThat(getAliasesResponse.getAliases().get("index2").size(), equalTo(1));
+            AliasMetaData aliasMetaData2 = getAliasesResponse.getAliases().get("index2").iterator().next();
+            assertThat(aliasMetaData2, notNullValue());
+            assertThat(aliasMetaData2.alias(), equalTo("alias2"));
+            assertThat(getAliasesResponse.getAliases().get("index3").size(), equalTo(0));
+        }
+    }
+
+    public void testGetAliasesNonExistentIndexOrAlias() throws IOException {
+        /*
+         * This test is quite extensive as this is the only way we can check that we haven't slid out of sync with the server
+         * because the server renders the xcontent in a spot that is difficult for us to access in a unit test.
+         */
+        String alias = "alias";
+        String index = "index";
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices(index);
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getException().getMessage(),
+                    equalTo("Elasticsearch exception [type=index_not_found_exception, reason=no such index]"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest(alias);
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getError(), equalTo("alias [" + alias + "] missing"));
+        }
+        createIndex(index, Settings.EMPTY);
+        client().performRequest(HttpPut.METHOD_NAME, index + "/_alias/" + alias);
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices(index, "non_existent_index");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getException().getMessage(),
+                    equalTo("Elasticsearch exception [type=index_not_found_exception, reason=no such index]"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices(index, "non_existent_index").aliases(alias);
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+            assertThat(getAliasesResponse.getException().getMessage(),
+                    equalTo("Elasticsearch exception [type=index_not_found_exception, reason=no such index]"));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices("non_existent_index*");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(0));
+        }
+        {
+            GetAliasesRequest getAliasesRequest = new GetAliasesRequest().indices(index).aliases(alias, "non_existent_alias");
+            GetAliasesResponse getAliasesResponse = execute(getAliasesRequest, highLevelClient().indices()::getAlias,
+                    highLevelClient().indices()::getAliasAsync);
+            assertThat(getAliasesResponse.status(), equalTo(RestStatus.NOT_FOUND));
+
+            assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
+            assertThat(getAliasesResponse.getAliases().get(index).size(), equalTo(1));
+            AliasMetaData aliasMetaData = getAliasesResponse.getAliases().get(index).iterator().next();
+            assertThat(aliasMetaData, notNullValue());
+            assertThat(aliasMetaData.alias(), equalTo(alias));
+            /*
+            This is the above response in json format:
+            {
+             "error": "alias [something] missing",
+             "status": 404,
+             "index": {
+               "aliases": {
+                 "alias": {}
+               }
+             }
+            }
+            */
+        }
+    }
+
     public void testIndexPutSettings() throws IOException {
 
         final Setting<Integer> dynamicSetting = IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING;

+ 31 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

@@ -94,6 +94,7 @@ import org.elasticsearch.common.lucene.uid.Versions;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.ByteSizeUnit;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.util.CollectionUtils;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentHelper;
@@ -1563,6 +1564,36 @@ public class RequestConvertersTests extends ESTestCase {
         assertEquals(expectedParams, request.getParameters());
     }
 
+    public void testGetAlias() {
+        GetAliasesRequest getAliasesRequest = new GetAliasesRequest();
+
+        Map<String, String> expectedParams = new HashMap<>();
+        setRandomLocal(getAliasesRequest, expectedParams);
+        setRandomIndicesOptions(getAliasesRequest::indicesOptions, getAliasesRequest::indicesOptions, expectedParams);
+
+        String[] indices = randomBoolean() ? null : randomIndicesNames(0, 2);
+        String[] aliases = randomBoolean() ? null : randomIndicesNames(0, 2);
+        getAliasesRequest.indices(indices);
+        getAliasesRequest.aliases(aliases);
+
+        Request request = RequestConverters.getAlias(getAliasesRequest);
+        StringJoiner expectedEndpoint = new StringJoiner("/", "/", "");
+
+        if (false == CollectionUtils.isEmpty(indices)) {
+            expectedEndpoint.add(String.join(",", indices));
+        }
+        expectedEndpoint.add("_alias");
+
+        if (false == CollectionUtils.isEmpty(aliases)) {
+            expectedEndpoint.add(String.join(",", aliases));
+        }
+
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        assertEquals(expectedEndpoint.toString(), request.getEndpoint());
+        assertEquals(expectedParams, request.getParameters());
+        assertNull(request.getEntity());
+    }
+
     public void testIndexPutSettings() throws IOException {
         String[] indices = randomBoolean() ? null : randomIndicesNames(0, 2);
         UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indices);

+ 74 - 2
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

@@ -51,10 +51,10 @@ import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
 import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
 import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
 import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
-import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
-import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
+import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
+import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
@@ -66,9 +66,11 @@ import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.DefaultShardOperationFailedException;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
+import org.elasticsearch.client.GetAliasesResponse;
 import org.elasticsearch.client.RequestOptions;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.client.SyncedFlushResponse;
+import org.elasticsearch.cluster.metadata.AliasMetaData;
 import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
 import org.elasticsearch.cluster.metadata.MappingMetaData;
 import org.elasticsearch.common.collect.ImmutableOpenMap;
@@ -87,6 +89,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -1726,6 +1729,75 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
 
+    public void testGetAlias() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            CreateIndexResponse createIndexResponse = client.indices().create(new CreateIndexRequest("index").alias(new Alias("alias")));
+            assertTrue(createIndexResponse.isAcknowledged());
+        }
+
+        {
+            // tag::get-alias-request
+            GetAliasesRequest request = new GetAliasesRequest();
+            GetAliasesRequest requestWithAlias = new GetAliasesRequest("alias1");
+            GetAliasesRequest requestWithAliases =
+                    new GetAliasesRequest(new String[]{"alias1", "alias2"});
+            // end::get-alias-request
+
+            // tag::get-alias-request-alias
+            request.aliases("alias"); // <1>
+            // end::get-alias-request-alias
+            // tag::get-alias-request-indices
+            request.indices("index"); // <1>
+            // end::get-alias-request-indices
+
+            // tag::get-alias-request-indicesOptions
+            request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1>
+            // end::get-alias-request-indicesOptions
+
+            // tag::get-alias-request-local
+            request.local(true); // <1>
+            // end::get-alias-request-local
+
+            // tag::get-alias-execute
+            GetAliasesResponse response = client.indices().getAlias(request, RequestOptions.DEFAULT);
+            // end::get-alias-execute
+
+            // tag::get-alias-response
+            Map<String, Set<AliasMetaData>> aliases = response.getAliases(); // <1>
+            // end::get-alias-response
+
+            assertThat(response.getAliases().get("index").size(), equalTo(1));
+            assertThat(response.getAliases().get("index").iterator().next().alias(), equalTo("alias"));
+
+            // tag::get-alias-listener
+            ActionListener<GetAliasesResponse> listener =
+                    new ActionListener<GetAliasesResponse>() {
+                        @Override
+                        public void onResponse(GetAliasesResponse getAliasesResponse) {
+                            // <1>
+                        }
+
+                        @Override
+                        public void onFailure(Exception e) {
+                            // <2>
+                        }
+            };
+            // end::get-alias-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::get-alias-execute-async
+            client.indices().getAliasAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::get-alias-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+    }
+
     public void testIndexPutSettings() throws Exception {
         RestHighLevelClient client = highLevelClient();
 

+ 94 - 0
docs/java-rest/high-level/indices/get_alias.asciidoc

@@ -0,0 +1,94 @@
+[[java-rest-high-get-alias]]
+=== Get Alias API
+
+[[java-rest-high-get-alias-request]]
+==== Get Alias Request
+
+The Get Alias API uses `GetAliasesRequest` as its request object.
+One or more aliases can be optionally provided either at construction
+time or later on through the relevant setter method.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-request]
+--------------------------------------------------
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-request-alias]
+--------------------------------------------------
+<1> One or more aliases to retrieve
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-request-indices]
+--------------------------------------------------
+<1> The index or indices that the alias is associated with
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-request-indicesOptions]
+--------------------------------------------------
+<1> Setting `IndicesOptions` controls how unavailable indices are resolved and
+how wildcard expressions are expanded when looking for aliases that belong to
+specified indices.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-request-local]
+--------------------------------------------------
+<1> The `local` flag (defaults to `false`) controls whether the aliases need
+to be looked up in the local cluster state or in the cluster state held by
+the elected master node
+
+[[java-rest-high-get-alias-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-execute]
+--------------------------------------------------
+
+[[java-rest-high-get-alias-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a get alias request requires both a `GetAliasesRequest`
+instance and an `ActionListener` instance to be passed to the asynchronous
+method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-execute-async]
+--------------------------------------------------
+<1> The `GetAliasesRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back using the `onResponse` method
+if the execution successfully completed or using the `onFailure` method if
+it failed.
+
+A typical listener for the `Boolean` response looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-listener]
+--------------------------------------------------
+<1> Called when the execution is successfully completed. The response is
+provided as an argument
+<2> Called in case of failure. The raised exception is provided as an argument
+
+[[java-rest-high-get-alias-response]]
+==== Get Alias Response
+
+The returned `GetAliasesResponse` allows to retrieve information about the
+executed operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-alias-response]
+--------------------------------------------------
+<1> Retrieves a map of indices and their aliases

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -80,6 +80,7 @@ Mapping Management::
 Alias Management::
 * <<java-rest-high-update-aliases>>
 * <<java-rest-high-exists-alias>>
+* <<java-rest-high-get-alias>>
 
 include::indices/create_index.asciidoc[]
 include::indices/delete_index.asciidoc[]
@@ -98,6 +99,7 @@ include::indices/put_mapping.asciidoc[]
 include::indices/get_mappings.asciidoc[]
 include::indices/update_aliases.asciidoc[]
 include::indices/exists_alias.asciidoc[]
+include::indices/get_alias.asciidoc[]
 include::indices/put_settings.asciidoc[]
 include::indices/get_settings.asciidoc[]
 include::indices/put_template.asciidoc[]

+ 1 - 1
server/src/main/java/org/elasticsearch/ElasticsearchException.java

@@ -420,7 +420,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
         return innerFromXContent(parser, false);
     }
 
-    private static ElasticsearchException innerFromXContent(XContentParser parser, boolean parseRootCauses) throws IOException {
+    public static ElasticsearchException innerFromXContent(XContentParser parser, boolean parseRootCauses) throws IOException {
         XContentParser.Token token = parser.currentToken();
         ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
 

+ 18 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesResponse.java

@@ -30,6 +30,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 public class GetAliasesResponse extends ActionResponse {
 
@@ -42,7 +43,6 @@ public class GetAliasesResponse extends ActionResponse {
     GetAliasesResponse() {
     }
 
-
     public ImmutableOpenMap<String, List<AliasMetaData>> getAliases() {
         return aliases;
     }
@@ -76,4 +76,21 @@ public class GetAliasesResponse extends ActionResponse {
             }
         }
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        GetAliasesResponse that = (GetAliasesResponse) o;
+        return Objects.equals(aliases, that.aliases);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(aliases);
+    }
 }

+ 0 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/TransportGetAliasesAction.java

@@ -65,5 +65,4 @@ public class TransportGetAliasesAction extends TransportMasterNodeReadAction<Get
         ImmutableOpenMap<String, List<AliasMetaData>> result = state.metaData().findAliases(request.aliases(), concreteIndices);
         listener.onResponse(new GetAliasesResponse(result));
     }
-
 }

+ 0 - 1
server/src/main/java/org/elasticsearch/cluster/metadata/AliasMetaData.java

@@ -348,5 +348,4 @@ public class AliasMetaData extends AbstractDiffable<AliasMetaData> implements To
             return builder.build();
         }
     }
-
 }

+ 6 - 13
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetAliasesAction.java

@@ -20,7 +20,6 @@
 package org.elasticsearch.rest.action.admin.indices;
 
 import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
-
 import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
 import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
 import org.elasticsearch.action.support.IndicesOptions;
@@ -84,6 +83,8 @@ public class RestGetAliasesAction extends BaseRestHandler {
         getAliasesRequest.indicesOptions(IndicesOptions.fromRequest(request, getAliasesRequest.indicesOptions()));
         getAliasesRequest.local(request.paramAsBoolean("local", getAliasesRequest.local()));
 
+        //we may want to move this logic to TransportGetAliasesAction but it is based on the original provided aliases, which will
+        //not always be available there (they may get replaced so retrieving request.aliases is not quite the same).
         return channel -> client.admin().indices().getAliases(getAliasesRequest, new RestBuilderListener<GetAliasesResponse>(channel) {
             @Override
             public RestResponse buildResponse(GetAliasesResponse response, XContentBuilder builder) throws Exception {
@@ -127,9 +128,11 @@ public class RestGetAliasesAction extends BaseRestHandler {
                         status = RestStatus.NOT_FOUND;
                         final String message;
                         if (difference.size() == 1) {
-                            message = String.format(Locale.ROOT, "alias [%s] missing", toNamesString(difference.iterator().next()));
+                            message = String.format(Locale.ROOT, "alias [%s] missing",
+                                    Strings.collectionToCommaDelimitedString(difference));
                         } else {
-                            message = String.format(Locale.ROOT, "aliases [%s] missing", toNamesString(difference.toArray(new String[0])));
+                            message = String.format(Locale.ROOT, "aliases [%s] missing",
+                                    Strings.collectionToCommaDelimitedString(difference));
                         }
                         builder.field("error", message);
                         builder.field("status", status.getStatus());
@@ -158,14 +161,4 @@ public class RestGetAliasesAction extends BaseRestHandler {
         });
     }
 
-    private static String toNamesString(final String... names) {
-        if (names == null || names.length == 0) {
-            return "";
-        } else if (names.length == 1) {
-            return names[0];
-        } else {
-            return Arrays.stream(names).collect(Collectors.joining(","));
-        }
-    }
-
 }

+ 139 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesResponseTests.java

@@ -0,0 +1,139 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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
+ *
+ *    http://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.elasticsearch.action.admin.indices.alias.get;
+
+import org.elasticsearch.cluster.metadata.AliasMetaData;
+import org.elasticsearch.cluster.metadata.AliasMetaData.Builder;
+import org.elasticsearch.common.collect.ImmutableOpenMap;
+import org.elasticsearch.test.AbstractStreamableTestCase;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class GetAliasesResponseTests extends AbstractStreamableTestCase<GetAliasesResponse> {
+
+    @Override
+    protected GetAliasesResponse createTestInstance() {
+        return createTestItem();
+    }
+
+    @Override
+    protected GetAliasesResponse createBlankInstance() {
+        return new GetAliasesResponse();
+    }
+
+    @Override
+    protected GetAliasesResponse mutateInstance(GetAliasesResponse response) {
+        return new GetAliasesResponse(mutateAliases(response.getAliases()));
+    }
+
+    private static ImmutableOpenMap<String, List<AliasMetaData>> mutateAliases(ImmutableOpenMap<String, List<AliasMetaData>> aliases) {
+        if (aliases.isEmpty()) {
+            return createIndicesAliasesMap(1, 3).build();
+        }
+
+        if (randomBoolean()) {
+            ImmutableOpenMap.Builder<String, List<AliasMetaData>> builder = ImmutableOpenMap.builder(aliases);
+            ImmutableOpenMap<String, List<AliasMetaData>> list = createIndicesAliasesMap(1, 2).build();
+            list.forEach(e -> builder.put(e.key, e.value));
+            return builder.build();
+        }
+
+        Set<String> indices = new HashSet<>();
+        Iterator<String> keys = aliases.keysIt();
+        while (keys.hasNext()) {
+            indices.add(keys.next());
+        }
+
+        List<String> indicesToBeModified = randomSubsetOf(randomIntBetween(1, indices.size()), indices);
+        ImmutableOpenMap.Builder<String, List<AliasMetaData>> builder = ImmutableOpenMap.builder();
+
+        for (String index : indices) {
+            List<AliasMetaData> list = new ArrayList<>(aliases.get(index));
+            if (indicesToBeModified.contains(index)) {
+                if (randomBoolean() || list.isEmpty()) {
+                    list.add(createAliasMetaData());
+                } else {
+                    int aliasIndex = randomInt(list.size() - 1);
+                    AliasMetaData aliasMetaData = list.get(aliasIndex);
+                    list.add(aliasIndex, mutateAliasMetaData(aliasMetaData));
+                }
+            }
+            builder.put(index, list);
+        }
+        return builder.build();
+    }
+
+    private static GetAliasesResponse createTestItem() {
+        return new GetAliasesResponse(createIndicesAliasesMap(0, 5).build());
+    }
+
+    private static ImmutableOpenMap.Builder<String, List<AliasMetaData>> createIndicesAliasesMap(int min, int max) {
+        ImmutableOpenMap.Builder<String, List<AliasMetaData>> builder = ImmutableOpenMap.builder();
+        int indicesNum = randomIntBetween(min, max);
+        for (int i = 0; i < indicesNum; i++) {
+            String index = randomAlphaOfLength(5);
+            List<AliasMetaData> aliasMetaData = new ArrayList<>();
+            int aliasesNum = randomIntBetween(0, 3);
+            for (int alias = 0; alias < aliasesNum; alias++) {
+                aliasMetaData.add(createAliasMetaData());
+            }
+            builder.put(index, aliasMetaData);
+        }
+        return builder;
+    }
+
+    public static AliasMetaData createAliasMetaData() {
+        Builder builder = AliasMetaData.builder(randomAlphaOfLengthBetween(3, 10));
+        if (randomBoolean()) {
+            builder.routing(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.searchRouting(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.indexRouting(randomAlphaOfLengthBetween(3, 10));
+        }
+        if (randomBoolean()) {
+            builder.filter("{\"term\":{\"year\":2016}}");
+        }
+        return builder.build();
+    }
+
+    public static AliasMetaData mutateAliasMetaData(AliasMetaData alias) {
+        boolean changeAlias = randomBoolean();
+        AliasMetaData.Builder builder = AliasMetaData.builder(changeAlias ? randomAlphaOfLengthBetween(2, 5) : alias.getAlias());
+        builder.searchRouting(alias.searchRouting());
+        builder.indexRouting(alias.indexRouting());
+        builder.filter(alias.filter());
+
+        if (false == changeAlias) {
+            if (randomBoolean()) {
+                builder.searchRouting(alias.searchRouting() + randomAlphaOfLengthBetween(1, 3));
+            } else {
+                builder.indexRouting(alias.indexRouting() + randomAlphaOfLengthBetween(1, 3));
+            }
+        }
+        return builder.build();
+    }
+}

+ 1 - 1
test/framework/src/main/java/org/elasticsearch/test/XContentTestUtils.java

@@ -60,7 +60,7 @@ public final class XContentTestUtils {
 
 
     /**
-     * Compares to maps generated from XContentObjects. The order of elements in arrays is ignored.
+     * Compares two maps generated from XContentObjects. The order of elements in arrays is ignored.
      *
      * @return null if maps are equal or path to the element where the difference was found
      */