Browse Source

Added Put Mapping API to high-level Rest client (#27869)

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

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

@@ -27,6 +27,8 @@ 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.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
 
@@ -89,6 +91,29 @@ public final class IndicesClient {
                 listener, Collections.emptySet(), headers);
     }
 
+    /**
+     * Updates the mappings on an index using the Put Mapping API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html">
+     * Put Mapping API on elastic.co</a>
+     */
+    public PutMappingResponse putMapping(PutMappingRequest putMappingRequest, Header... headers) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(putMappingRequest, Request::putMapping, PutMappingResponse::fromXContent,
+                Collections.emptySet(), headers);
+    }
+
+    /**
+     * Asynchronously updates the mappings on an index using the Put Mapping API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html">
+     * Put Mapping API on elastic.co</a>
+     */
+    public void putMappingAsync(PutMappingRequest putMappingRequest, ActionListener<PutMappingResponse> listener,
+                                       Header... headers) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(putMappingRequest, Request::putMapping, PutMappingResponse::fromXContent,
+            listener, Collections.emptySet(), headers);
+    }
+
     /**
      * Opens an index using the Open Index API
      * <p>

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

@@ -32,6 +32,7 @@ import org.elasticsearch.action.DocWriteRequest;
 import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.delete.DeleteRequest;
@@ -178,6 +179,22 @@ public final class Request {
         return new Request(HttpPut.METHOD_NAME, endpoint, parameters.getParams(), entity);
     }
 
+    static Request putMapping(PutMappingRequest putMappingRequest) throws IOException {
+        // The concreteIndex is an internal concept, not applicable to requests made over the REST API.
+        if (putMappingRequest.getConcreteIndex() != null) {
+            throw new IllegalArgumentException("concreteIndex cannot be set on PutMapping requests made over the REST API");
+        }
+
+        String endpoint = endpoint(putMappingRequest.indices(), "_mapping", putMappingRequest.type());
+
+        Params parameters = Params.builder();
+        parameters.withTimeout(putMappingRequest.timeout());
+        parameters.withMasterTimeout(putMappingRequest.masterNodeTimeout());
+
+        HttpEntity entity = createEntity(putMappingRequest, 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);
     }
@@ -454,6 +471,10 @@ public final class Request {
         return endpoint(String.join(",", indices), String.join(",", types), endpoint);
     }
 
+    static String endpoint(String[] indices, String endpoint, String type) {
+        return endpoint(String.join(",", indices), endpoint, type);
+    }
+
     /**
      * Utility method to build request's endpoint.
      */

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

@@ -27,6 +27,8 @@ 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.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
 import org.elasticsearch.action.support.IndicesOptions;
@@ -108,6 +110,35 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    @SuppressWarnings("unchecked")
+    public void testPutMapping() throws IOException {
+        {
+            // Add mappings to index
+            String indexName = "mapping_index";
+            createIndex(indexName);
+
+            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);
+            assertTrue(putMappingResponse.isAcknowledged());
+
+            Map<String, Object> indexMetaData = getIndexMetadata(indexName);
+            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

+ 34 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java

@@ -28,6 +28,7 @@ import org.elasticsearch.action.DocWriteRequest;
 import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.bulk.BulkShardRequest;
@@ -317,6 +318,39 @@ public class RequestTests extends ESTestCase {
         assertToXContentBody(createIndexRequest, request.getEntity());
     }
 
+    public void testPutMapping() throws IOException {
+        PutMappingRequest putMappingRequest = new PutMappingRequest();
+
+        int numIndices = randomIntBetween(0, 5);
+        String[] indices = new String[numIndices];
+        for (int i = 0; i < numIndices; i++) {
+            indices[i] = "index-" + randomAlphaOfLengthBetween(2, 5);
+        }
+        putMappingRequest.indices(indices);
+
+        String type = randomAlphaOfLengthBetween(3, 10);
+        putMappingRequest.type(type);
+
+        Map<String, String> expectedParams = new HashMap<>();
+
+        setRandomTimeout(putMappingRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams);
+        setRandomMasterTimeout(putMappingRequest, expectedParams);
+
+        Request request = Request.putMapping(putMappingRequest);
+        StringJoiner endpoint = new StringJoiner("/", "/", "");
+        String index = String.join(",", indices);
+        if (Strings.hasLength(index)) {
+            endpoint.add(index);
+        }
+        endpoint.add("_mapping");
+        endpoint.add(type);
+        assertEquals(endpoint.toString(), request.getEndpoint());
+
+        assertEquals(expectedParams, request.getParameters());
+        assertEquals("PUT", request.getMethod());
+        assertToXContentBody(putMappingRequest, request.getEntity());
+    }
+
     public void testDeleteIndex() {
         String[] indices = randomIndicesNames(0, 5);
         DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indices);

+ 89 - 7
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

@@ -28,6 +28,8 @@ 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.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
 import org.elasticsearch.action.support.ActiveShardCount;
@@ -157,15 +159,15 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
 
             // tag::create-index-request-mappings
             request.mapping("tweet", // <1>
-                "  {\n" +
-                "    \"tweet\": {\n" +
-                "      \"properties\": {\n" +
-                "        \"message\": {\n" +
-                "          \"type\": \"text\"\n" +
-                "        }\n" +
+                "{\n" +
+                "  \"tweet\": {\n" +
+                "    \"properties\": {\n" +
+                "      \"message\": {\n" +
+                "        \"type\": \"text\"\n" +
                 "      }\n" +
                 "    }\n" +
-                "  }", // <2>
+                "  }\n" +
+                "}", // <2>
                 XContentType.JSON);
             // end::create-index-request-mappings
 
@@ -228,6 +230,86 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         }
     }
 
+    public void testPutMapping() throws IOException {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            CreateIndexResponse createIndexResponse = client.indices().create(new CreateIndexRequest("twitter"));
+            assertTrue(createIndexResponse.isAcknowledged());
+        }
+
+        {
+            // tag::put-mapping-request
+            PutMappingRequest request = new PutMappingRequest("twitter"); // <1>
+            request.type("tweet"); // <2>
+            // end::put-mapping-request
+
+            // tag::put-mapping-request-source
+            request.source(
+                "{\n" +
+                "  \"tweet\": {\n" +
+                "    \"properties\": {\n" +
+                "      \"message\": {\n" +
+                "        \"type\": \"text\"\n" +
+                "      }\n" +
+                "    }\n" +
+                "  }\n" +
+                "}", // <1>
+                XContentType.JSON);
+            // end::put-mapping-request-source
+
+            // tag::put-mapping-request-timeout
+            request.timeout(TimeValue.timeValueMinutes(2)); // <1>
+            request.timeout("2m"); // <2>
+            // end::put-mapping-request-timeout
+            // tag::put-mapping-request-masterTimeout
+            request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+            request.masterNodeTimeout("1m"); // <2>
+            // end::put-mapping-request-masterTimeout
+
+            // tag::put-mapping-execute
+            PutMappingResponse putMappingResponse = client.indices().putMapping(request);
+            // end::put-mapping-execute
+
+            // tag::put-mapping-response
+            boolean acknowledged = putMappingResponse.isAcknowledged(); // <1>
+            // end::put-mapping-response
+            assertTrue(acknowledged);
+        }
+    }
+
+    public void testPutMappingAsync() throws Exception {
+        final RestHighLevelClient client = highLevelClient();
+
+        {
+            CreateIndexResponse createIndexResponse = client.indices().create(new CreateIndexRequest("twitter"));
+            assertTrue(createIndexResponse.isAcknowledged());
+        }
+
+        {
+            PutMappingRequest request = new PutMappingRequest("twitter").type("tweet");
+            // tag::put-mapping-execute-async
+            client.indices().putMappingAsync(request, new ActionListener<PutMappingResponse>() {
+                @Override
+                public void onResponse(PutMappingResponse putMappingResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            });
+            // end::put-mapping-execute-async
+
+            assertBusy(() -> {
+                // TODO Use Indices Exist API instead once it exists
+                Response response = client.getLowLevelClient().performRequest("HEAD", "twitter");
+                assertTrue(RestStatus.OK.getStatus() == response.getStatusLine().getStatusCode());
+            });
+        }
+    }
+
     public void testOpenIndex() throws IOException {
         RestHighLevelClient client = highLevelClient();
 

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

@@ -6,6 +6,8 @@ include::open_index.asciidoc[]
 
 include::close_index.asciidoc[]
 
+include::putmapping.asciidoc[]
+
 include::_index.asciidoc[]
 
 include::get.asciidoc[]

+ 71 - 0
docs/java-rest/high-level/apis/putmapping.asciidoc

@@ -0,0 +1,71 @@
+[[java-rest-high-put-mapping]]
+=== Put Mapping API
+
+[[java-rest-high-put-mapping-request]]
+==== Put Mapping Request
+
+A `PutMappingRequest` requires an `index` argument, and a type:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-request]
+--------------------------------------------------
+<1> The index to add the mapping to
+<2> The type to create (or update)
+
+==== Mapping source
+A description of the fields to create on the mapping; if not defined, the mapping will default to empty.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-request-source]
+--------------------------------------------------
+<1> The mapping source
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-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 creation as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-request-masterTimeout]
+--------------------------------------------------
+<1> Timeout to connect to the master node as a `TimeValue`
+<2> Timeout to connect to the master node as a `String`
+
+[[java-rest-high-put-mapping-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-execute]
+--------------------------------------------------
+
+[[java-rest-high-put-mapping-async]]
+==== Asynchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-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-put-mapping-response]]
+==== Put Mapping Response
+
+The returned `PutMappingResponse` allows to retrieve information about the executed
+ operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[put-mapping-response]
+--------------------------------------------------
+<1> Indicates whether all of the nodes have acknowledged the request

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

@@ -8,6 +8,7 @@ Indices APIs::
 * <<java-rest-high-delete-index>>
 * <<java-rest-high-open-index>>
 * <<java-rest-high-close-index>>
+* <<java-rest-high-put-mapping>>
 
 Single document APIs::
 * <<java-rest-high-document-index>>

+ 12 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingRequest.java

@@ -32,6 +32,7 @@ import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentHelper;
@@ -57,7 +58,7 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
  * @see org.elasticsearch.client.IndicesAdminClient#putMapping(PutMappingRequest)
  * @see PutMappingResponse
  */
-public class PutMappingRequest extends AcknowledgedRequest<PutMappingRequest> implements IndicesRequest.Replaceable {
+public class PutMappingRequest extends AcknowledgedRequest<PutMappingRequest> implements IndicesRequest.Replaceable, ToXContentObject {
 
     private static ObjectHashSet<String> RESERVED_FIELDS = ObjectHashSet.from(
             "_uid", "_id", "_type", "_source",  "_all", "_analyzer", "_parent", "_routing", "_index",
@@ -318,4 +319,14 @@ public class PutMappingRequest extends AcknowledgedRequest<PutMappingRequest> im
         }
         out.writeOptionalWriteable(concreteIndex);
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        if (source != null) {
+            builder.rawValue(new BytesArray(source), XContentType.JSON);
+        } else {
+            builder.startObject().endObject();
+        }
+        return builder;
+    }
 }

+ 24 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponse.java

@@ -22,13 +22,24 @@ package org.elasticsearch.action.admin.indices.mapping.put;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
 
 /**
  * The response of put mapping operation.
  */
-public class PutMappingResponse extends AcknowledgedResponse {
+public class PutMappingResponse extends AcknowledgedResponse implements ToXContentObject {
+
+    private static final ConstructingObjectParser<PutMappingResponse, Void> PARSER = new ConstructingObjectParser<>("put_mapping",
+        true, args -> new PutMappingResponse((boolean) args[0]));
+
+    static {
+        declareAcknowledgedField(PARSER);
+    }
 
     protected PutMappingResponse() {
 
@@ -49,4 +60,16 @@ public class PutMappingResponse extends AcknowledgedResponse {
         super.writeTo(out);
         writeAcknowledged(out);
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        addAcknowledgedField(builder);
+        builder.endObject();
+        return builder;
+    }
+
+    public static PutMappingResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.apply(parser, null);
+    }
 }

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

@@ -115,7 +115,7 @@ public class CreateIndexRequestTests extends ESTestCase {
         final XContentType xContentType = randomFrom(XContentType.values());
         BytesReference originalBytes = toShuffledXContent(createIndexRequest, xContentType, EMPTY_PARAMS, humanReadable);
 
-        CreateIndexRequest parsedCreateIndexRequest = new CreateIndexRequest(createIndexRequest.index());
+        CreateIndexRequest parsedCreateIndexRequest = new CreateIndexRequest();
         parsedCreateIndexRequest.source(originalBytes, xContentType);
 
         assertMappingsEqual(createIndexRequest.mappings(), parsedCreateIndexRequest.mappings());
@@ -201,7 +201,7 @@ public class CreateIndexRequestTests extends ESTestCase {
         return builder;
     }
 
-    private static void randomMappingFields(XContentBuilder builder, boolean allowObjectField) throws IOException {
+    public static void randomMappingFields(XContentBuilder builder, boolean allowObjectField) throws IOException {
         builder.startObject("properties");
 
         int fieldsNo = randomIntBetween(0, 5);

+ 84 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingRequestTests.java

@@ -21,17 +21,26 @@ package org.elasticsearch.action.admin.indices.mapping.put;
 
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestTests;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.common.xcontent.yaml.YamlXContent;
 import org.elasticsearch.index.Index;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
 
+import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS;
+
 public class PutMappingRequestTests extends ESTestCase {
 
     public void testValidation() {
@@ -94,4 +103,79 @@ public class PutMappingRequestTests extends ESTestCase {
             }
         }
     }
+
+    public void testToXContent() throws IOException {
+        PutMappingRequest request = new PutMappingRequest("foo");
+        request.type("my_type");
+
+        XContentBuilder mapping = JsonXContent.contentBuilder().startObject();
+        mapping.startObject("properties");
+        mapping.startObject("email");
+        mapping.field("type", "text");
+        mapping.endObject();
+        mapping.endObject();
+        mapping.endObject();
+        request.source(mapping);
+
+        String actualRequestBody = Strings.toString(request);
+        String expectedRequestBody = "{\"properties\":{\"email\":{\"type\":\"text\"}}}";
+        assertEquals(expectedRequestBody, actualRequestBody);
+    }
+
+    public void testToXContentWithEmptySource() throws IOException {
+        PutMappingRequest request = new PutMappingRequest("foo");
+        request.type("my_type");
+
+        String actualRequestBody = Strings.toString(request);
+        String expectedRequestBody = "{}";
+        assertEquals(expectedRequestBody, actualRequestBody);
+    }
+
+    public void testToAndFromXContent() throws IOException {
+
+        final PutMappingRequest putMappingRequest = createTestItem();
+
+        boolean humanReadable = randomBoolean();
+        final XContentType xContentType = randomFrom(XContentType.values());
+        BytesReference originalBytes = toShuffledXContent(putMappingRequest, xContentType, EMPTY_PARAMS, humanReadable);
+
+        PutMappingRequest parsedPutMappingRequest = new PutMappingRequest();
+        parsedPutMappingRequest.source(originalBytes, xContentType);
+
+        assertMappingsEqual(putMappingRequest.source(), parsedPutMappingRequest.source());
+    }
+
+    private void assertMappingsEqual(String expected, String actual) throws IOException {
+
+        XContentParser expectedJson = createParser(XContentType.JSON.xContent(), expected);
+        XContentParser actualJson = createParser(XContentType.JSON.xContent(), actual);
+        assertEquals(expectedJson.mapOrdered(), actualJson.mapOrdered());
+    }
+
+    /**
+     * Returns a random {@link PutMappingRequest}.
+     */
+    private static PutMappingRequest createTestItem() throws IOException {
+        String index = randomAlphaOfLength(5);
+
+        PutMappingRequest request = new PutMappingRequest(index);
+
+        String type = randomAlphaOfLength(5);
+        request.type(type);
+        request.source(randomMapping());
+
+        return request;
+    }
+
+    private static XContentBuilder randomMapping() throws IOException {
+        XContentBuilder builder = XContentFactory.jsonBuilder();
+        builder.startObject();
+
+        if (randomBoolean()) {
+            CreateIndexRequestTests.randomMappingFields(builder, true);
+        }
+
+        builder.endObject();
+        return builder;
+    }
 }

+ 85 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/mapping/put/PutMappingResponseTests.java

@@ -0,0 +1,85 @@
+/*
+ * 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.mapping.put;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+
+import static org.elasticsearch.test.XContentTestUtils.insertRandomFields;
+
+public class PutMappingResponseTests extends ESTestCase {
+
+    public void testToXContent() {
+        PutMappingResponse response = new PutMappingResponse(true);
+        String output = Strings.toString(response);
+        assertEquals("{\"acknowledged\":true}", output);
+    }
+
+    public void testToAndFromXContent() throws IOException {
+        doFromXContentTestWithRandomFields(false);
+    }
+
+    /**
+     * This test adds random fields and objects to the xContent rendered out to
+     * ensure we can parse it back to be forward compatible with additions to
+     * the xContent
+     */
+    public void testFromXContentWithRandomFields() throws IOException {
+        doFromXContentTestWithRandomFields(true);
+    }
+
+    private void doFromXContentTestWithRandomFields(boolean addRandomFields) throws IOException {
+
+        final PutMappingResponse putMappingResponse = createTestItem();
+
+        boolean humanReadable = randomBoolean();
+        final XContentType xContentType = randomFrom(XContentType.values());
+        BytesReference originalBytes = toShuffledXContent(putMappingResponse, xContentType, ToXContent.EMPTY_PARAMS, humanReadable);
+
+        BytesReference mutated;
+        if (addRandomFields) {
+            mutated = insertRandomFields(xContentType, originalBytes, null, random());
+        } else {
+            mutated = originalBytes;
+        }
+        PutMappingResponse parsedPutMappingResponse;
+        try (XContentParser parser = createParser(xContentType.xContent(), mutated)) {
+            parsedPutMappingResponse = PutMappingResponse.fromXContent(parser);
+            assertNull(parser.nextToken());
+        }
+
+        assertEquals(putMappingResponse.isAcknowledged(), parsedPutMappingResponse.isAcknowledged());
+    }
+
+    /**
+     * Returns a random {@link PutMappingResponse}.
+     */
+    private static PutMappingResponse createTestItem() throws IOException {
+        boolean acknowledged = randomBoolean();
+
+        return new PutMappingResponse(acknowledged);
+    }
+}