Browse Source

Added Create Index support to high-level REST client (#27351)

Relates to #27205
Catalin Ursachi 7 years ago
parent
commit
f823cea79c

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

@@ -21,6 +21,8 @@ package org.elasticsearch.client;
 
 import org.apache.http.Header;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
 
@@ -29,13 +31,13 @@ import java.util.Collections;
 
 /**
  * A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Indices API.
- *
+ * <p>
  * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html">Indices API on elastic.co</a>
  */
 public final class IndicesClient {
     private final RestHighLevelClient restHighLevelClient;
 
-    public IndicesClient(RestHighLevelClient restHighLevelClient) {
+    IndicesClient(RestHighLevelClient restHighLevelClient) {
         this.restHighLevelClient = restHighLevelClient;
     }
 
@@ -56,8 +58,32 @@ public final class IndicesClient {
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-delete-index.html">
      * Delete Index API on elastic.co</a>
      */
-    public void deleteIndexAsync(DeleteIndexRequest deleteIndexRequest, ActionListener<DeleteIndexResponse> listener, Header... headers) {
+    public void deleteIndexAsync(DeleteIndexRequest deleteIndexRequest, ActionListener<DeleteIndexResponse> listener,
+                                       Header... headers) {
         restHighLevelClient.performRequestAsyncAndParseEntity(deleteIndexRequest, Request::deleteIndex, DeleteIndexResponse::fromXContent,
             listener, Collections.emptySet(), headers);
     }
+
+    /**
+     * Creates an index using the Create Index API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html">
+     * Create Index API on elastic.co</a>
+     */
+    public CreateIndexResponse createIndex(CreateIndexRequest createIndexRequest, Header... headers) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(createIndexRequest, Request::createIndex, CreateIndexResponse::fromXContent,
+            Collections.emptySet(), headers);
+    }
+
+    /**
+     * Asynchronously creates an index using the Create Index API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html">
+     * Create Index API on elastic.co</a>
+     */
+    public void createIndexAsync(CreateIndexRequest createIndexRequest, ActionListener<CreateIndexResponse> listener,
+                                       Header... headers) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(createIndexRequest, Request::createIndex, CreateIndexResponse::fromXContent,
+            listener, Collections.emptySet(), headers);
+    }
 }

+ 21 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java

@@ -29,6 +29,7 @@ import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.entity.ContentType;
 import org.apache.lucene.util.BytesRef;
 import org.elasticsearch.action.DocWriteRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.delete.DeleteRequest;
@@ -137,6 +138,19 @@ public final class Request {
         return new Request(HttpDelete.METHOD_NAME, endpoint, parameters.getParams(), null);
     }
 
+    static Request createIndex(CreateIndexRequest createIndexRequest) throws IOException {
+        String endpoint = endpoint(createIndexRequest.indices(), Strings.EMPTY_ARRAY, "");
+
+        Params parameters = Params.builder();
+        parameters.withTimeout(createIndexRequest.timeout());
+        parameters.withMasterTimeout(createIndexRequest.masterNodeTimeout());
+        parameters.withWaitForActiveShards(createIndexRequest.waitForActiveShards());
+        parameters.withUpdateAllTypes(createIndexRequest.updateAllTypes());
+
+        HttpEntity entity = createEntity(createIndexRequest, REQUEST_BODY_CONTENT_TYPE);
+        return new Request(HttpPut.METHOD_NAME, endpoint, parameters.getParams(), entity);
+    }
+
     static Request info() {
         return new Request(HttpGet.METHOD_NAME, "/", Collections.emptyMap(), null);
     }
@@ -534,6 +548,13 @@ public final class Request {
             return putParam("timeout", timeout);
         }
 
+        Params withUpdateAllTypes(boolean updateAllTypes) {
+            if (updateAllTypes) {
+                return putParam("update_all_types", Boolean.TRUE.toString());
+            }
+            return this;
+        }
+
         Params withVersion(long version) {
             if (version != Versions.MATCH_ANY) {
                 return putParam("version", Long.toString(version));

+ 88 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java

@@ -20,14 +20,88 @@
 package org.elasticsearch.client;
 
 import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.indices.alias.Alias;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
+import java.util.Map;
+
+import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
 
 public class IndicesClientIT extends ESRestHighLevelClientTestCase {
 
+    @SuppressWarnings("unchecked")
+    public void testCreateIndex() throws IOException {
+        {
+            // Create index
+            String indexName = "plain_index";
+            assertFalse(indexExists(indexName));
+
+            CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
+
+            CreateIndexResponse createIndexResponse =
+                execute(createIndexRequest, highLevelClient().indices()::createIndex, highLevelClient().indices()::createIndexAsync);
+            assertTrue(createIndexResponse.isAcknowledged());
+
+            assertTrue(indexExists(indexName));
+        }
+        {
+            // Create index with mappings, aliases and settings
+            String indexName = "rich_index";
+            assertFalse(indexExists(indexName));
+
+            CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
+
+            Alias alias = new Alias("alias_name");
+            alias.filter("{\"term\":{\"year\":2016}}");
+            alias.routing("1");
+            createIndexRequest.alias(alias);
+
+            Settings.Builder settings = Settings.builder();
+            settings.put(SETTING_NUMBER_OF_REPLICAS, 2);
+            createIndexRequest.settings(settings);
+
+            XContentBuilder mappingBuilder = JsonXContent.contentBuilder();
+            mappingBuilder.startObject().startObject("properties").startObject("field");
+            mappingBuilder.field("type", "text");
+            mappingBuilder.endObject().endObject().endObject();
+            createIndexRequest.mapping("type_name", mappingBuilder);
+
+            CreateIndexResponse createIndexResponse =
+                execute(createIndexRequest, highLevelClient().indices()::createIndex, highLevelClient().indices()::createIndexAsync);
+            assertTrue(createIndexResponse.isAcknowledged());
+
+            Map<String, Object> indexMetaData = getIndexMetadata(indexName);
+
+            Map<String, Object> settingsData = (Map) indexMetaData.get("settings");
+            Map<String, Object> indexSettings = (Map) settingsData.get("index");
+            assertEquals("2", indexSettings.get("number_of_replicas"));
+
+            Map<String, Object> aliasesData = (Map) indexMetaData.get("aliases");
+            Map<String, Object> aliasData = (Map) aliasesData.get("alias_name");
+            assertEquals("1", aliasData.get("index_routing"));
+            Map<String, Object> filter = (Map) aliasData.get("filter");
+            Map<String, Object> term = (Map) filter.get("term");
+            assertEquals(2016, term.get("year"));
+
+            Map<String, Object> mappingsData = (Map) indexMetaData.get("mappings");
+            Map<String, Object> typeData = (Map) mappingsData.get("type_name");
+            Map<String, Object> properties = (Map) typeData.get("properties");
+            Map<String, Object> field = (Map) properties.get("field");
+
+            assertEquals("text", field.get("type"));
+        }
+    }
+
     public void testDeleteIndex() throws IOException {
         {
             // Delete index if exists
@@ -65,4 +139,18 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
 
         return response.getStatusLine().getStatusCode() == 200;
     }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> getIndexMetadata(String index) throws IOException {
+        Response response = client().performRequest("GET", index);
+
+        XContentType entityContentType = XContentType.fromMediaTypeOrFormat(response.getEntity().getContentType().getValue());
+        Map<String, Object> responseEntity = XContentHelper.convertToMap(entityContentType.xContent(), response.getEntity().getContent(),
+            false);
+
+        Map<String, Object> indexMetaData = (Map) responseEntity.get(index);
+        assertNotNull(indexMetaData);
+
+        return indexMetaData;
+    }
 }

+ 39 - 5
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java

@@ -25,6 +25,7 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.util.EntityUtils;
 import org.elasticsearch.action.DocWriteRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.bulk.BulkShardRequest;
@@ -36,6 +37,7 @@ import org.elasticsearch.action.search.MultiSearchRequest;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchScrollRequest;
 import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
@@ -253,6 +255,34 @@ public class RequestTests extends ESTestCase {
         assertEquals(method, request.getMethod());
     }
 
+    public void testCreateIndex() throws IOException {
+        CreateIndexRequest createIndexRequest = new CreateIndexRequest();
+
+        String indexName = "index-" + randomAlphaOfLengthBetween(2, 5);
+
+        createIndexRequest.index(indexName);
+
+        Map<String, String> expectedParams = new HashMap<>();
+
+        setRandomTimeout(createIndexRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams);
+        setRandomMasterTimeout(createIndexRequest, expectedParams);
+        setRandomWaitForActiveShards(createIndexRequest::waitForActiveShards, expectedParams);
+
+        if (randomBoolean()) {
+            boolean updateAllTypes = randomBoolean();
+            createIndexRequest.updateAllTypes(updateAllTypes);
+            if (updateAllTypes) {
+                expectedParams.put("update_all_types", Boolean.TRUE.toString());
+            }
+        }
+
+        Request request = Request.createIndex(createIndexRequest);
+        assertEquals("/" + indexName, request.getEndpoint());
+        assertEquals(expectedParams, request.getParameters());
+        assertEquals("PUT", request.getMethod());
+        assertToXContentBody(createIndexRequest, request.getEntity());
+    }
+
     public void testDeleteIndex() throws IOException {
         DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest();
 
@@ -407,11 +437,7 @@ public class RequestTests extends ESTestCase {
                 expectedParams.put("refresh", refreshPolicy.getValue());
             }
         }
-        if (randomBoolean()) {
-            int waitForActiveShards = randomIntBetween(0, 10);
-            updateRequest.waitForActiveShards(waitForActiveShards);
-            expectedParams.put("wait_for_active_shards", String.valueOf(waitForActiveShards));
-        }
+        setRandomWaitForActiveShards(updateRequest::waitForActiveShards, expectedParams);
         if (randomBoolean()) {
             long version = randomLong();
             updateRequest.version(version);
@@ -1016,6 +1042,14 @@ public class RequestTests extends ESTestCase {
         }
     }
 
+    private static void setRandomWaitForActiveShards(Consumer<Integer> setter, Map<String, String> expectedParams) {
+        if (randomBoolean()) {
+            int waitForActiveShards = randomIntBetween(0, 10);
+            setter.accept(waitForActiveShards);
+            expectedParams.put("wait_for_active_shards", String.valueOf(waitForActiveShards));
+        }
+    }
+
     private static void setRandomRefreshPolicy(ReplicatedWriteRequest<?> request, Map<String, String> expectedParams) {
         if (randomBoolean()) {
             WriteRequest.RefreshPolicy refreshPolicy = randomFrom(WriteRequest.RefreshPolicy.values());

+ 97 - 20
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

@@ -21,13 +21,18 @@ package org.elasticsearch.client.documentation;
 
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.admin.indices.alias.Alias;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
+import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
-import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
@@ -52,8 +57,8 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         RestHighLevelClient client = highLevelClient();
 
         {
-            Response createIndexResponse = client().performRequest("PUT", "/posts");
-            assertEquals(200, createIndexResponse.getStatusLine().getStatusCode());
+            CreateIndexResponse createIndexResponse = client.indices().createIndex(new CreateIndexRequest("posts"));
+            assertTrue(createIndexResponse.isAcknowledged());
         }
 
         {
@@ -61,14 +66,26 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
             DeleteIndexRequest request = new DeleteIndexRequest("posts"); // <1>
             // end::delete-index-request
 
+            // tag::delete-index-request-timeout
+            request.timeout(TimeValue.timeValueMinutes(2)); // <1>
+            request.timeout("2m"); // <2>
+            // end::delete-index-request-timeout
+            // tag::delete-index-request-masterTimeout
+            request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+            request.masterNodeTimeout("1m"); // <2>
+            // end::delete-index-request-masterTimeout
+            // tag::delete-index-request-indicesOptions
+            request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1>
+            // end::delete-index-request-indicesOptions
+
             // tag::delete-index-execute
             DeleteIndexResponse deleteIndexResponse = client.indices().deleteIndex(request);
             // end::delete-index-execute
-            assertTrue(deleteIndexResponse.isAcknowledged());
 
             // tag::delete-index-response
             boolean acknowledged = deleteIndexResponse.isAcknowledged(); // <1>
             // end::delete-index-response
+            assertTrue(acknowledged);
 
             // tag::delete-index-execute-async
             client.indices().deleteIndexAsync(request, new ActionListener<DeleteIndexResponse>() {
@@ -85,26 +102,11 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
             // end::delete-index-execute-async
         }
 
-        {
-            DeleteIndexRequest request = new DeleteIndexRequest("posts");
-            // tag::delete-index-request-timeout
-            request.timeout(TimeValue.timeValueMinutes(2)); // <1>
-            request.timeout("2m"); // <2>
-            // end::delete-index-request-timeout
-            // tag::delete-index-request-masterTimeout
-            request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
-            request.timeout("1m"); // <2>
-            // end::delete-index-request-masterTimeout
-            // tag::delete-index-request-indicesOptions
-            request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1>
-            // end::delete-index-request-indicesOptions
-        }
-
         {
             // tag::delete-index-notfound
             try {
                 DeleteIndexRequest request = new DeleteIndexRequest("does_not_exist");
-                DeleteIndexResponse deleteIndexResponse = client.indices().deleteIndex(request);
+                client.indices().deleteIndex(request);
             } catch (ElasticsearchException exception) {
                 if (exception.status() == RestStatus.NOT_FOUND) {
                     // <1>
@@ -113,4 +115,79 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
             // end::delete-index-notfound
         }
     }
+
+    public void testCreateIndex() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            // tag::create-index-request
+            CreateIndexRequest request = new CreateIndexRequest("twitter"); // <1>
+            // end::create-index-request
+
+            // tag::create-index-request-settings
+            request.settings(Settings.builder() // <1>
+                .put("index.number_of_shards", 3)
+                .put("index.number_of_replicas", 2)
+            );
+            // end::create-index-request-settings
+
+            // tag::create-index-request-mappings
+            request.mapping("tweet", // <1>
+                "  {\n" +
+                "    \"tweet\": {\n" +
+                "      \"properties\": {\n" +
+                "        \"message\": {\n" +
+                "          \"type\": \"text\"\n" +
+                "        }\n" +
+                "      }\n" +
+                "    }\n" +
+                "  }", // <2>
+                XContentType.JSON);
+            // end::create-index-request-mappings
+
+            // tag::create-index-request-aliases
+            request.alias(
+                new Alias("twitter_alias")  // <1>
+            );
+            // end::create-index-request-aliases
+
+            // tag::create-index-request-timeout
+            request.timeout(TimeValue.timeValueMinutes(2)); // <1>
+            request.timeout("2m"); // <2>
+            // end::create-index-request-timeout
+            // tag::create-index-request-masterTimeout
+            request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+            request.masterNodeTimeout("1m"); // <2>
+            // end::create-index-request-masterTimeout
+            // tag::create-index-request-waitForActiveShards
+            request.waitForActiveShards(2); // <1>
+            request.waitForActiveShards(ActiveShardCount.DEFAULT); // <2>
+            // end::create-index-request-waitForActiveShards
+
+            // tag::create-index-execute
+            CreateIndexResponse createIndexResponse = client.indices().createIndex(request);
+            // end::create-index-execute
+
+            // tag::create-index-response
+            boolean acknowledged = createIndexResponse.isAcknowledged(); // <1>
+            boolean shardsAcked = createIndexResponse.isShardsAcked(); // <2>
+            // end::create-index-response
+            assertTrue(acknowledged);
+            assertTrue(shardsAcked);
+
+            // tag::create-index-execute-async
+            client.indices().createIndexAsync(request, new ActionListener<CreateIndexResponse>() {
+                @Override
+                public void onResponse(CreateIndexResponse createIndexResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            });
+            // end::create-index-execute-async
+        }
+    }
 }

+ 37 - 5
core/src/main/java/org/elasticsearch/action/admin/indices/alias/Alias.java

@@ -21,10 +21,13 @@ package org.elasticsearch.action.admin.indices.alias;
 
 import org.elasticsearch.ElasticsearchGenerationException;
 import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Streamable;
 import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentParser;
@@ -33,11 +36,17 @@ import org.elasticsearch.index.query.QueryBuilder;
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Represents an alias, to be associated with an index
  */
-public class Alias implements Streamable {
+public class Alias implements Streamable, ToXContentObject {
+
+    private static final ParseField FILTER = new ParseField("filter");
+    private static final ParseField ROUTING = new ParseField("routing");
+    private static final ParseField INDEX_ROUTING = new ParseField("index_routing", "indexRouting", "index-routing");
+    private static final ParseField SEARCH_ROUTING = new ParseField("search_routing", "searchRouting", "search-routing");
 
     private String name;
 
@@ -196,16 +205,16 @@ public class Alias implements Streamable {
             if (token == XContentParser.Token.FIELD_NAME) {
                 currentFieldName = parser.currentName();
             } else if (token == XContentParser.Token.START_OBJECT) {
-                if ("filter".equals(currentFieldName)) {
+                if (FILTER.match(currentFieldName)) {
                     Map<String, Object> filter = parser.mapOrdered();
                     alias.filter(filter);
                 }
             } else if (token == XContentParser.Token.VALUE_STRING) {
-                if ("routing".equals(currentFieldName)) {
+                if (ROUTING.match(currentFieldName)) {
                     alias.routing(parser.text());
-                } else if ("index_routing".equals(currentFieldName) || "indexRouting".equals(currentFieldName) || "index-routing".equals(currentFieldName)) {
+                } else if (INDEX_ROUTING.match(currentFieldName)) {
                     alias.indexRouting(parser.text());
-                } else if ("search_routing".equals(currentFieldName) || "searchRouting".equals(currentFieldName) || "search-routing".equals(currentFieldName)) {
+                } else if (SEARCH_ROUTING.match(currentFieldName)) {
                     alias.searchRouting(parser.text());
                 }
             }
@@ -213,6 +222,29 @@ public class Alias implements Streamable {
         return alias;
     }
 
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject(name);
+
+        if (filter != null) {
+            builder.rawField(FILTER.getPreferredName(), new BytesArray(filter), XContentType.JSON);
+        }
+
+        if (indexRouting != null && indexRouting.equals(searchRouting)) {
+            builder.field(ROUTING.getPreferredName(), indexRouting);
+        } else {
+            if (indexRouting != null) {
+                builder.field(INDEX_ROUTING.getPreferredName(), indexRouting);
+            }
+            if (searchRouting != null) {
+                builder.field(SEARCH_ROUTING.getPreferredName(), searchRouting);
+            }
+        }
+
+        builder.endObject();
+        return builder;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;

+ 38 - 4
core/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequest.java

@@ -30,6 +30,7 @@ import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.collect.MapBuilder;
@@ -37,6 +38,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentHelper;
@@ -65,7 +67,11 @@ import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
  * @see org.elasticsearch.client.Requests#createIndexRequest(String)
  * @see CreateIndexResponse
  */
-public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest> implements IndicesRequest {
+public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest> implements IndicesRequest, ToXContentObject {
+
+    private static final ParseField MAPPINGS = new ParseField("mappings");
+    private static final ParseField SETTINGS = new ParseField("settings");
+    private static final ParseField ALIASES = new ParseField("aliases");
 
     private String cause = "";
 
@@ -376,14 +382,14 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
     public CreateIndexRequest source(Map<String, ?> source) {
         for (Map.Entry<String, ?> entry : source.entrySet()) {
             String name = entry.getKey();
-            if (name.equals("settings")) {
+            if (SETTINGS.match(name)) {
                 settings((Map<String, Object>) entry.getValue());
-            } else if (name.equals("mappings")) {
+            } else if (MAPPINGS.match(name)) {
                 Map<String, Object> mappings = (Map<String, Object>) entry.getValue();
                 for (Map.Entry<String, Object> entry1 : mappings.entrySet()) {
                     mapping(entry1.getKey(), (Map<String, Object>) entry1.getValue());
                 }
-            } else if (name.equals("aliases")) {
+            } else if (ALIASES.match(name)) {
                 aliases((Map<String, Object>) entry.getValue());
             } else {
                 // maybe custom?
@@ -520,4 +526,32 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
         out.writeBoolean(updateAllTypes);
         waitForActiveShards.writeTo(out);
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+
+        builder.startObject(SETTINGS.getPreferredName());
+        settings.toXContent(builder, params);
+        builder.endObject();
+
+        builder.startObject(MAPPINGS.getPreferredName());
+        for (Map.Entry<String, String> entry : mappings.entrySet()) {
+            builder.rawField(entry.getKey(), new BytesArray(entry.getValue()), XContentType.JSON);
+        }
+        builder.endObject();
+
+        builder.startObject(ALIASES.getPreferredName());
+        for (Alias alias : aliases) {
+            alias.toXContent(builder, params);
+        }
+        builder.endObject();
+
+        for (Map.Entry<String, IndexMetaData.Custom> entry : customs.entrySet()) {
+            builder.field(entry.getKey(), entry.getValue(), params);
+        }
+
+        builder.endObject();
+        return builder;
+    }
 }

+ 6 - 9
core/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponse.java

@@ -39,20 +39,17 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
  */
 public class CreateIndexResponse extends AcknowledgedResponse implements ToXContentObject {
 
-    private static final String SHARDS_ACKNOWLEDGED = "shards_acknowledged";
-    private static final String INDEX = "index";
-
-    private static final ParseField SHARDS_ACKNOWLEDGED_PARSER = new ParseField(SHARDS_ACKNOWLEDGED);
-    private static final ParseField INDEX_PARSER = new ParseField(INDEX);
+    private static final ParseField SHARDS_ACKNOWLEDGED = new ParseField("shards_acknowledged");
+    private static final ParseField INDEX = new ParseField("index");
 
     private static final ConstructingObjectParser<CreateIndexResponse, Void> PARSER = new ConstructingObjectParser<>("create_index",
         true, args -> new CreateIndexResponse((boolean) args[0], (boolean) args[1], (String) args[2]));
 
     static {
         declareAcknowledgedField(PARSER);
-        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), SHARDS_ACKNOWLEDGED_PARSER,
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), SHARDS_ACKNOWLEDGED,
             ObjectParser.ValueType.BOOLEAN);
-        PARSER.declareField(constructorArg(), (parser, context) -> parser.text(), INDEX_PARSER, ObjectParser.ValueType.STRING);
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.text(), INDEX, ObjectParser.ValueType.STRING);
     }
 
     private boolean shardsAcked;
@@ -102,8 +99,8 @@ public class CreateIndexResponse extends AcknowledgedResponse implements ToXCont
     }
 
     public void addCustomFields(XContentBuilder builder) throws IOException {
-        builder.field(SHARDS_ACKNOWLEDGED, isShardsAcked());
-        builder.field(INDEX, index());
+        builder.field(SHARDS_ACKNOWLEDGED.getPreferredName(), isShardsAcked());
+        builder.field(INDEX.getPreferredName(), index());
     }
 
     @Override

+ 3 - 4
core/src/main/java/org/elasticsearch/action/support/master/AcknowledgedResponse.java

@@ -37,11 +37,10 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
  */
 public abstract class AcknowledgedResponse extends ActionResponse {
 
-    private static final String ACKNOWLEDGED = "acknowledged";
-    private static final ParseField ACKNOWLEDGED_PARSER = new ParseField(ACKNOWLEDGED);
+    private static final ParseField ACKNOWLEDGED = new ParseField("acknowledged");
 
     protected static <T extends AcknowledgedResponse> void declareAcknowledgedField(ConstructingObjectParser<T, Void> PARSER) {
-        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), ACKNOWLEDGED_PARSER,
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), ACKNOWLEDGED,
             ObjectParser.ValueType.BOOLEAN);
     }
 
@@ -78,6 +77,6 @@ public abstract class AcknowledgedResponse extends ActionResponse {
     }
 
     protected void addAcknowledgedField(XContentBuilder builder) throws IOException {
-        builder.field(ACKNOWLEDGED, isAcknowledged());
+        builder.field(ACKNOWLEDGED.getPreferredName(), isAcknowledged());
     }
 }

+ 1 - 1
core/src/main/java/org/elasticsearch/common/settings/Settings.java

@@ -624,7 +624,7 @@ public final class Settings implements ToXContentFragment {
     }
 
     /**
-     * Parsers the generated xconten from {@link Settings#toXContent(XContentBuilder, Params)} into a new Settings object.
+     * Parsers the generated xcontent from {@link Settings#toXContent(XContentBuilder, Params)} into a new Settings object.
      * Note this method requires the parser to either be positioned on a null token or on
      * {@link org.elasticsearch.common.xcontent.XContentParser.Token#START_OBJECT}.
      */

+ 175 - 2
core/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequestTests.java

@@ -20,13 +20,26 @@
 package org.elasticsearch.action.admin.indices.create;
 
 import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.action.admin.indices.alias.Alias;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
+import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
+import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS;
 
 public class CreateIndexRequestTests extends ESTestCase {
 
@@ -46,7 +59,7 @@ public class CreateIndexRequestTests extends ESTestCase {
             }
         }
     }
-    
+
     public void testTopLevelKeys() throws IOException {
         String createIndex =
                 "{\n"
@@ -65,8 +78,168 @@ public class CreateIndexRequestTests extends ESTestCase {
                 + "}";
 
         CreateIndexRequest request = new CreateIndexRequest();
-        ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, 
+        ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
                 () -> {request.source(createIndex, XContentType.JSON);});
         assertEquals("unknown key [FOO_SHOULD_BE_ILLEGAL_HERE] for create index", e.getMessage());
     }
+
+    public void testToXContent() throws IOException {
+        CreateIndexRequest request = new CreateIndexRequest("foo");
+
+        String mapping = JsonXContent.contentBuilder().startObject().startObject("type").endObject().endObject().string();
+        request.mapping("my_type", mapping, XContentType.JSON);
+
+        Alias alias = new Alias("test_alias");
+        alias.routing("1");
+        alias.filter("{\"term\":{\"year\":2016}}");
+        request.alias(alias);
+
+        Settings.Builder settings = Settings.builder();
+        settings.put(SETTING_NUMBER_OF_SHARDS, 10);
+        request.settings(settings);
+
+        String actualRequestBody = Strings.toString(request);
+
+        String expectedRequestBody = "{\"settings\":{\"index\":{\"number_of_shards\":\"10\"}}," +
+            "\"mappings\":{\"my_type\":{\"type\":{}}}," +
+            "\"aliases\":{\"test_alias\":{\"filter\":{\"term\":{\"year\":2016}},\"routing\":\"1\"}}}";
+
+        assertEquals(expectedRequestBody, actualRequestBody);
+    }
+
+    public void testToAndFromXContent() throws IOException {
+
+        final CreateIndexRequest createIndexRequest = createTestItem();
+
+        boolean humanReadable = randomBoolean();
+        final XContentType xContentType = randomFrom(XContentType.values());
+        BytesReference originalBytes = toShuffledXContent(createIndexRequest, xContentType, EMPTY_PARAMS, humanReadable);
+
+        CreateIndexRequest parsedCreateIndexRequest = new CreateIndexRequest(createIndexRequest.index());
+        parsedCreateIndexRequest.source(originalBytes, xContentType);
+
+        assertMappingsEqual(createIndexRequest.mappings(), parsedCreateIndexRequest.mappings());
+        assertAliasesEqual(createIndexRequest.aliases(), parsedCreateIndexRequest.aliases());
+        assertEquals(createIndexRequest.settings(), parsedCreateIndexRequest.settings());
+    }
+
+    private void assertMappingsEqual(Map<String, String> expected, Map<String, String> actual) throws IOException {
+        assertEquals(expected.keySet(), actual.keySet());
+
+        for (Map.Entry<String, String> expectedEntry : expected.entrySet()) {
+            String expectedValue = expectedEntry.getValue();
+            String actualValue = actual.get(expectedEntry.getKey());
+            XContentParser expectedJson = createParser(XContentType.JSON.xContent(), expectedValue);
+            XContentParser actualJson = createParser(XContentType.JSON.xContent(), actualValue);
+            assertEquals(expectedJson.mapOrdered(), actualJson.mapOrdered());
+        }
+    }
+
+    private static void assertAliasesEqual(Set<Alias> expected, Set<Alias> actual) throws IOException {
+        assertEquals(expected, actual);
+
+        for (Alias expectedAlias : expected) {
+            for (Alias actualAlias : actual) {
+                if (expectedAlias.equals(actualAlias)) {
+                    // As Alias#equals only looks at name, we check the equality of the other Alias parameters here.
+                    assertEquals(expectedAlias.filter(), actualAlias.filter());
+                    assertEquals(expectedAlias.indexRouting(), actualAlias.indexRouting());
+                    assertEquals(expectedAlias.searchRouting(), actualAlias.searchRouting());
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns a random {@link CreateIndexRequest}.
+     */
+    private static CreateIndexRequest createTestItem() throws IOException {
+        String index = randomAlphaOfLength(5);
+
+        CreateIndexRequest request = new CreateIndexRequest(index);
+
+        int aliasesNo = randomIntBetween(0, 2);
+        for (int i = 0; i < aliasesNo; i++) {
+            request.alias(randomAlias());
+        }
+
+        if (randomBoolean()) {
+            String type = randomAlphaOfLength(5);
+            request.mapping(type, randomMapping(type));
+        }
+
+        if (randomBoolean()) {
+            request.settings(randomIndexSettings());
+        }
+
+        return request;
+    }
+
+    private static Settings randomIndexSettings() {
+        Settings.Builder builder = Settings.builder();
+
+        if (randomBoolean()) {
+            int numberOfShards = randomIntBetween(1, 10);
+            builder.put(SETTING_NUMBER_OF_SHARDS, numberOfShards);
+        }
+
+        if (randomBoolean()) {
+            int numberOfReplicas = randomIntBetween(1, 10);
+            builder.put(SETTING_NUMBER_OF_REPLICAS, numberOfReplicas);
+        }
+
+        return builder.build();
+    }
+
+    private static XContentBuilder randomMapping(String type) throws IOException {
+        XContentBuilder builder = XContentFactory.jsonBuilder();
+        builder.startObject().startObject(type);
+
+        randomMappingFields(builder, true);
+
+        builder.endObject().endObject();
+        return builder;
+    }
+
+    private static void randomMappingFields(XContentBuilder builder, boolean allowObjectField) throws IOException {
+        builder.startObject("properties");
+
+        int fieldsNo = randomIntBetween(0, 5);
+        for (int i = 0; i < fieldsNo; i++) {
+            builder.startObject(randomAlphaOfLength(5));
+
+            if (allowObjectField && randomBoolean()) {
+                randomMappingFields(builder, false);
+            } else {
+                builder.field("type", "text");
+            }
+
+            builder.endObject();
+        }
+
+        builder.endObject();
+    }
+
+    private static Alias randomAlias() {
+        Alias alias = new Alias(randomAlphaOfLength(5));
+
+        if (randomBoolean()) {
+            if (randomBoolean()) {
+                alias.routing(randomAlphaOfLength(5));
+            } else {
+                if (randomBoolean()) {
+                    alias.indexRouting(randomAlphaOfLength(5));
+                }
+                if (randomBoolean()) {
+                    alias.searchRouting(randomAlphaOfLength(5));
+                }
+            }
+        }
+
+        if (randomBoolean()) {
+            alias.filter("{\"term\":{\"year\":2016}}");
+        }
+
+        return alias;
+    }
 }

+ 97 - 0
docs/java-rest/high-level/apis/createindex.asciidoc

@@ -0,0 +1,97 @@
+[[java-rest-high-create-index]]
+=== Create Index API
+
+[[java-rest-high-create-index-request]]
+==== Create Index Request
+
+A `CreateIndexRequest` requires an `index` argument:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request]
+--------------------------------------------------
+<1> The index to create
+
+==== Index settings
+Each index created can have specific settings associated with it.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-settings]
+--------------------------------------------------
+<1> Settings for this index
+
+==== Index mappings
+An index may be created with mappings for its document types
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-mappings]
+--------------------------------------------------
+<1> The type to define
+<2> The mapping for this type, provided as a JSON string
+
+==== Index aliases
+Aliases can be set at index creation time
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-aliases]
+--------------------------------------------------
+<1> The alias to define
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-timeout]
+--------------------------------------------------
+<1> Timeout to wait for the all the nodes to acknowledge the index creation as a `TimeValue`
+<2> Timeout to wait for the all the nodes to acknowledge the index creatiom as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-masterTimeout]
+--------------------------------------------------
+<1> Timeout to connect to the master node as a `TimeValue`
+<2> Timeout to connect to the master node as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-waitForActiveShards]
+--------------------------------------------------
+<1> The number of active shard copies to wait for before proceeding with the operation, as an `int`.
+<2> The number of active shard copies to wait for before proceeding with the operation, as an `ActiveShardCount`.
+
+[[java-rest-high-create-index-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-execute]
+--------------------------------------------------
+
+[[java-rest-high-create-index-async]]
+==== Asynchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-execute-async]
+--------------------------------------------------
+<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-create-index-response]]
+==== Create Index Response
+
+The returned `CreateIndexResponse` allows to retrieve information about the executed
+ operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-response]
+--------------------------------------------------
+<1> Indicates whether all of the nodes have acknowledged the request
+<2> Indicates whether the requisite number of shard copies were started for each shard in the index before timing out

+ 1 - 1
docs/java-rest/high-level/apis/deleteindex.asciidoc

@@ -65,7 +65,7 @@ The returned `DeleteIndexResponse` allows to retrieve information about the exec
 --------------------------------------------------
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[delete-index-response]
 --------------------------------------------------
-<1> Indicates whether all of the nodes have acknowledged the request or not
+<1> Indicates whether all of the nodes have acknowledged the request
 
 If the index was not found, an `ElasticsearchException` will be thrown:
 

+ 1 - 0
docs/java-rest/high-level/apis/index.asciidoc

@@ -1,3 +1,4 @@
+include::createindex.asciidoc[]
 include::deleteindex.asciidoc[]
 include::_index.asciidoc[]
 include::get.asciidoc[]

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

@@ -4,6 +4,7 @@
 The Java High Level REST Client supports the following APIs:
 
 Indices APIs::
+* <<java-rest-high-create-index>>
 * <<java-rest-high-delete-index>>
 
 Single document APIs::