diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java index 961e8f1699a54..f3bfa43ececfd 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java @@ -38,8 +38,8 @@ import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; +import org.elasticsearch.client.indices.GetFieldMappingsRequest; +import org.elasticsearch.client.indices.GetFieldMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; @@ -329,11 +329,16 @@ public void getMappingAsync(GetMappingsRequest getMappingsRequest, RequestOption * @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 + * + * @deprecated This method uses an old request object which still refers to types, a deprecated feature. The method + * {@link #getFieldMapping(GetFieldMappingsRequest, RequestOptions)} should be used instead, which accepts a new request object. */ - public GetFieldMappingsResponse getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest, - RequestOptions options) throws IOException { - return restHighLevelClient.performRequestAndParseEntity(getFieldMappingsRequest, IndicesRequestConverters::getFieldMapping, options, - GetFieldMappingsResponse::fromXContent, emptySet()); + @Deprecated + public org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse getFieldMapping( + org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest getFieldMappingsRequest, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(getFieldMappingsRequest, IndicesRequestConverters::getFieldMapping, + options, org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse::fromXContent, emptySet()); } /** @@ -343,9 +348,45 @@ public GetFieldMappingsResponse getFieldMapping(GetFieldMappingsRequest getField * @param getFieldMappingsRequest 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 + * + * @deprecated This method uses an old request object which still refers to types, a deprecated feature. The + * method {@link #getFieldMappingAsync(GetFieldMappingsRequest, RequestOptions, ActionListener)} should be used instead, + * which accepts a new request object. + */ + @Deprecated + public void getFieldMappingAsync(org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest getFieldMappingsRequest, + RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(getFieldMappingsRequest, IndicesRequestConverters::getFieldMapping, options, + org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse::fromXContent, listener, emptySet()); + } + + /** + * Retrieves the field mappings on an index or indices using the Get Field Mapping API. + * See + * Get Field Mapping API on elastic.co + * @param getFieldMappingsRequest 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 GetFieldMappingsResponse getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest, + RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(getFieldMappingsRequest, IndicesRequestConverters::getFieldMapping, + options, GetFieldMappingsResponse::fromXContent, emptySet() + ); + } + + /** + * Asynchronously retrieves the field mappings on an index or indices using the Get Field Mapping API. + * See + * Get Field Mapping API on elastic.co + * @param getFieldMappingsRequest 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 getFieldMappingAsync(GetFieldMappingsRequest getFieldMappingsRequest, RequestOptions options, - ActionListener listener) { + public void getFieldMappingAsync(GetFieldMappingsRequest getFieldMappingsRequest, + RequestOptions options, ActionListener listener) { restHighLevelClient.performRequestAsyncAndParseEntity(getFieldMappingsRequest, IndicesRequestConverters::getFieldMapping, options, GetFieldMappingsResponse::fromXContent, listener, emptySet()); } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java index 798b1d9582155..bf8427ea770ce 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java @@ -35,7 +35,7 @@ import org.elasticsearch.action.admin.indices.flush.SyncedFlushRequest; import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; +import org.elasticsearch.client.indices.GetFieldMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; @@ -164,7 +164,29 @@ static Request getMappings(GetMappingsRequest getMappingsRequest) throws IOExcep return request; } - static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest) throws IOException { + static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest) { + String[] indices = getFieldMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.indices(); + String[] fields = getFieldMappingsRequest.fields() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.fields(); + + String endpoint = new RequestConverters.EndpointBuilder() + .addCommaSeparatedPathParts(indices) + .addPathPartAsIs("_mapping") + .addPathPartAsIs("field") + .addCommaSeparatedPathParts(fields) + .build(); + + Request request = new Request(HttpGet.METHOD_NAME, endpoint); + + RequestConverters.Params parameters = new RequestConverters.Params(request); + parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions()); + parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults()); + parameters.withLocal(getFieldMappingsRequest.local()); + parameters.putParam(INCLUDE_TYPE_NAME_PARAMETER, "false"); + + return request; + } + + static Request getFieldMapping(org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest getFieldMappingsRequest) { String[] indices = getFieldMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.indices(); String[] types = getFieldMappingsRequest.types() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.types(); String[] fields = getFieldMappingsRequest.fields() == null ? Strings.EMPTY_ARRAY : getFieldMappingsRequest.fields(); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsRequest.java new file mode 100644 index 0000000000000..da5fd5c2bc0ae --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsRequest.java @@ -0,0 +1,90 @@ +/* + * 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.indices; + +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.Strings; + +/** Request the mappings of specific fields */ +public class GetFieldMappingsRequest implements Validatable { + + private boolean local = false; + + private String[] fields = Strings.EMPTY_ARRAY; + + private boolean includeDefaults = false; + + private String[] indices = Strings.EMPTY_ARRAY; + + private IndicesOptions indicesOptions = IndicesOptions.strictExpandOpen(); + + /** + * Indicate whether the receiving node should operate based on local index information or forward requests, + * where needed, to other nodes. If running locally, request will not raise errors if running locally & missing indices. + */ + public GetFieldMappingsRequest local(boolean local) { + this.local = local; + return this; + } + + public boolean local() { + return local; + } + + public GetFieldMappingsRequest indices(String... indices) { + this.indices = indices; + return this; + } + + public GetFieldMappingsRequest indicesOptions(IndicesOptions indicesOptions) { + this.indicesOptions = indicesOptions; + return this; + } + + public String[] indices() { + return indices; + } + + public IndicesOptions indicesOptions() { + return indicesOptions; + } + + /** @param fields a list of fields to retrieve the mapping for */ + public GetFieldMappingsRequest fields(String... fields) { + this.fields = fields; + return this; + } + + public String[] fields() { + return fields; + } + + public boolean includeDefaults() { + return includeDefaults; + } + + /** Indicates whether default mapping settings should be returned */ + public GetFieldMappingsRequest includeDefaults(boolean includeDefaults) { + this.includeDefaults = includeDefaults; + return this; + } + +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsResponse.java new file mode 100644 index 0000000000000..b982eab235485 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/indices/GetFieldMappingsResponse.java @@ -0,0 +1,190 @@ +/* + * 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.indices; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.mapper.Mapper; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +/** Response object for {@link GetFieldMappingsRequest} API */ +public class GetFieldMappingsResponse { + + private static final ParseField MAPPINGS = new ParseField("mappings"); + + private static final ObjectParser, String> PARSER = + new ObjectParser<>(MAPPINGS.getPreferredName(), true, HashMap::new); + + static { + PARSER.declareField((p, fieldMappings, index) -> { + p.nextToken(); + while (p.currentToken() == XContentParser.Token.FIELD_NAME) { + final String fieldName = p.currentName(); + final FieldMappingMetaData fieldMappingMetaData = FieldMappingMetaData.fromXContent(p); + fieldMappings.put(fieldName, fieldMappingMetaData); + p.nextToken(); + } + }, MAPPINGS, ObjectParser.ValueType.OBJECT); + } + + private Map> mappings; + + GetFieldMappingsResponse(Map> mappings) { + this.mappings = mappings; + } + + + /** + * Returns the fields mapping. The return map keys are indexes and fields (as specified in the request). + */ + public Map> mappings() { + return mappings; + } + + /** + * Returns the mappings of a specific index and field. + * + * @param field field name as specified in the {@link GetFieldMappingsRequest} + * @return FieldMappingMetaData for the requested field or null if not found. + */ + public FieldMappingMetaData fieldMappings(String index, String field) { + Map indexMapping = mappings.get(index); + if (indexMapping == null) { + return null; + } + return indexMapping.get(field); + } + + + public static GetFieldMappingsResponse fromXContent(XContentParser parser) throws IOException { + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + final Map> mappings = new HashMap<>(); + if (parser.nextToken() == XContentParser.Token.FIELD_NAME) { + while (parser.currentToken() == XContentParser.Token.FIELD_NAME) { + final String index = parser.currentName(); + final Map fieldMappings = PARSER.parse(parser, index); + mappings.put(index, fieldMappings); + parser.nextToken(); + } + } + return new GetFieldMappingsResponse(mappings); + } + + public static class FieldMappingMetaData { + private static final ParseField FULL_NAME = new ParseField("full_name"); + private static final ParseField MAPPING = new ParseField("mapping"); + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("field_mapping_meta_data", true, + a -> new FieldMappingMetaData((String)a[0], (BytesReference)a[1]) + ); + + static { + PARSER.declareField(optionalConstructorArg(), + (p, c) -> p.text(), FULL_NAME, ObjectParser.ValueType.STRING); + PARSER.declareField(optionalConstructorArg(), + (p, c) -> { + final XContentBuilder jsonBuilder = jsonBuilder().copyCurrentStructure(p); + final BytesReference bytes = BytesReference.bytes(jsonBuilder); + return bytes; + }, MAPPING, ObjectParser.ValueType.OBJECT); + } + + private String fullName; + private BytesReference source; + + public FieldMappingMetaData(String fullName, BytesReference source) { + this.fullName = fullName; + this.source = source; + } + + public String fullName() { + return fullName; + } + + /** + * Returns the mappings as a map. Note that the returned map has a single key which is always the field's {@link Mapper#name}. + */ + public Map sourceAsMap() { + return XContentHelper.convertToMap(source, true, XContentType.JSON).v2(); + } + + //pkg-private for testing + BytesReference getSource() { + return source; + } + + public static FieldMappingMetaData fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + @Override + public String toString() { + return "FieldMappingMetaData{fullName='" + fullName + '\'' + ", source=" + source + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FieldMappingMetaData)) return false; + FieldMappingMetaData that = (FieldMappingMetaData) o; + return Objects.equals(fullName, that.fullName) && Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(fullName, source); + } + } + + + @Override + public String toString() { + return "GetFieldMappingsResponse{" + "mappings=" + mappings + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GetFieldMappingsResponse)) return false; + GetFieldMappingsResponse that = (GetFieldMappingsResponse) o; + return Objects.equals(mappings, that.mappings); + } + + @Override + public int hashCode() { + return Objects.hash(mappings); + } + +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java index 0e7c8be2c3407..3d348ea93b35d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java @@ -43,8 +43,8 @@ import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; +import org.elasticsearch.client.indices.GetFieldMappingsRequest; +import org.elasticsearch.client.indices.GetFieldMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; @@ -94,6 +94,7 @@ import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.rest.action.admin.indices.RestGetFieldMappingAction; import org.elasticsearch.rest.action.admin.indices.RestPutMappingAction; import java.io.IOException; @@ -511,7 +512,6 @@ public void testGetFieldMapping() throws IOException { GetFieldMappingsRequest getFieldMappingsRequest = new GetFieldMappingsRequest() .indices(indexName) - .types("_doc") .fields("field"); GetFieldMappingsResponse getFieldMappingsResponse = @@ -520,7 +520,7 @@ public void testGetFieldMapping() throws IOException { highLevelClient().indices()::getFieldMappingAsync); final Map fieldMappingMap = - getFieldMappingsResponse.mappings().get(indexName).get("_doc"); + getFieldMappingsResponse.mappings().get(indexName); final GetFieldMappingsResponse.FieldMappingMetaData metaData = new GetFieldMappingsResponse.FieldMappingMetaData("field", @@ -528,6 +528,42 @@ public void testGetFieldMapping() throws IOException { assertThat(fieldMappingMap, equalTo(Collections.singletonMap("field", metaData))); } + public void testGetFieldMappingWithTypes() throws IOException { + String indexName = "test"; + createIndex(indexName, Settings.EMPTY); + + PutMappingRequest putMappingRequest = new PutMappingRequest(indexName); + XContentBuilder mappingBuilder = JsonXContent.contentBuilder(); + mappingBuilder.startObject().startObject("properties").startObject("field"); + mappingBuilder.field("type", "text"); + mappingBuilder.endObject().endObject().endObject(); + putMappingRequest.source(mappingBuilder); + + AcknowledgedResponse putMappingResponse = + execute(putMappingRequest, highLevelClient().indices()::putMapping, highLevelClient().indices()::putMappingAsync); + assertTrue(putMappingResponse.isAcknowledged()); + + org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest getFieldMappingsRequest = + new org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest() + .indices(indexName) + .types("_doc") + .fields("field"); + + org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse getFieldMappingsResponse = + execute(getFieldMappingsRequest, + highLevelClient().indices()::getFieldMapping, + highLevelClient().indices()::getFieldMappingAsync, + expectWarnings(RestGetFieldMappingAction.TYPES_DEPRECATION_MESSAGE)); + + final Map + fieldMappingMap = getFieldMappingsResponse.mappings().get(indexName).get("_doc"); + + final org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData metaData = + new org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData("field", + new BytesArray("{\"field\":{\"type\":\"text\"}}")); + assertThat(fieldMappingMap, equalTo(Collections.singletonMap("field", metaData))); + } + public void testDeleteIndex() throws IOException { { // Delete index if exists diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java index 16690aa2fc8e7..ae9024cbf57d0 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java @@ -38,7 +38,6 @@ import org.elasticsearch.action.admin.indices.flush.SyncedFlushRequest; import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; @@ -55,6 +54,7 @@ import org.elasticsearch.client.indices.GetIndexTemplatesRequest; import org.elasticsearch.client.indices.IndexTemplatesExistRequest; import org.elasticsearch.client.indices.PutMappingRequest; +import org.elasticsearch.client.indices.GetFieldMappingsRequest; import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; @@ -258,7 +258,7 @@ public void testGetMapping() throws IOException { Assert.assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod())); } - public void testGetFieldMapping() throws IOException { + public void testGetFieldMapping() { GetFieldMappingsRequest getFieldMappingsRequest = new GetFieldMappingsRequest(); String[] indices = Strings.EMPTY_ARRAY; @@ -269,6 +269,51 @@ public void testGetFieldMapping() throws IOException { getFieldMappingsRequest.indices((String[]) null); } + String[] fields = null; + if (ESTestCase.randomBoolean()) { + fields = new String[ESTestCase.randomIntBetween(1, 5)]; + for (int i = 0; i < fields.length; i++) { + fields[i] = ESTestCase.randomAlphaOfLengthBetween(3, 10); + } + getFieldMappingsRequest.fields(fields); + } else if (ESTestCase.randomBoolean()) { + getFieldMappingsRequest.fields((String[]) null); + } + + Map expectedParams = new HashMap<>(); + RequestConvertersTests.setRandomIndicesOptions(getFieldMappingsRequest::indicesOptions, getFieldMappingsRequest::indicesOptions, + expectedParams); + RequestConvertersTests.setRandomLocal(getFieldMappingsRequest::local, expectedParams); + expectedParams.put(INCLUDE_TYPE_NAME_PARAMETER, "false"); + + Request request = IndicesRequestConverters.getFieldMapping(getFieldMappingsRequest); + StringJoiner endpoint = new StringJoiner("/", "/", ""); + String index = String.join(",", indices); + if (Strings.hasLength(index)) { + endpoint.add(index); + } + endpoint.add("_mapping"); + endpoint.add("field"); + if (fields != null) { + endpoint.add(String.join(",", fields)); + } + Assert.assertThat(endpoint.toString(), equalTo(request.getEndpoint())); + Assert.assertThat(expectedParams, equalTo(request.getParameters())); + Assert.assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod())); + } + + public void testGetFieldMappingWithTypes() { + org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest getFieldMappingsRequest = + new org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest(); + + String[] indices = Strings.EMPTY_ARRAY; + if (ESTestCase.randomBoolean()) { + indices = RequestConvertersTests.randomIndicesNames(0, 5); + getFieldMappingsRequest.indices(indices); + } else if (ESTestCase.randomBoolean()) { + getFieldMappingsRequest.indices((String[]) null); + } + String type = null; if (ESTestCase.randomBoolean()) { type = ESTestCase.randomAlphaOfLengthBetween(3, 10); @@ -289,7 +334,6 @@ public void testGetFieldMapping() throws IOException { } Map expectedParams = new HashMap<>(); - RequestConvertersTests.setRandomIndicesOptions(getFieldMappingsRequest::indicesOptions, getFieldMappingsRequest::indicesOptions, expectedParams); RequestConvertersTests.setRandomLocal(getFieldMappingsRequest::local, expectedParams); @@ -309,7 +353,6 @@ public void testGetFieldMapping() throws IOException { endpoint.add(String.join(",", fields)); } Assert.assertThat(endpoint.toString(), equalTo(request.getEndpoint())); - Assert.assertThat(expectedParams, equalTo(request.getParameters())); Assert.assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod())); } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java index 94bb0a085e7a3..b6af3e26d4d12 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java @@ -42,8 +42,8 @@ import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.get.GetIndexResponse; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; -import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; +import org.elasticsearch.client.indices.GetFieldMappingsRequest; +import org.elasticsearch.client.indices.GetFieldMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; @@ -727,8 +727,7 @@ public void testGetFieldMapping() throws IOException, InterruptedException { // tag::get-field-mappings-request GetFieldMappingsRequest request = new GetFieldMappingsRequest(); // <1> request.indices("twitter"); // <2> - request.types("_doc"); // <3> - request.fields("message", "timestamp"); // <4> + request.fields("message", "timestamp"); // <3> // end::get-field-mappings-request // tag::get-field-mappings-request-indicesOptions @@ -747,12 +746,12 @@ public void testGetFieldMapping() throws IOException, InterruptedException { // end::get-field-mappings-execute // tag::get-field-mappings-response - final Map>> mappings = + final Map> mappings = response.mappings();// <1> - final Map typeMappings = - mappings.get("twitter").get("_doc"); // <2> + final Map fieldMappings = + mappings.get("twitter"); // <2> final GetFieldMappingsResponse.FieldMappingMetaData metaData = - typeMappings.get("message");// <3> + fieldMappings.get("message");// <3> final String fullName = metaData.fullName();// <4> final Map source = metaData.sourceAsMap(); // <5> @@ -779,11 +778,11 @@ public void onFailure(Exception e) { final CountDownLatch latch = new CountDownLatch(1); final ActionListener latchListener = new LatchedActionListener<>(listener, latch); listener = ActionListener.wrap(r -> { - final Map>> mappings = + final Map> mappings = r.mappings(); - final Map typeMappings = - mappings.get("twitter").get("_doc"); - final GetFieldMappingsResponse.FieldMappingMetaData metaData1 = typeMappings.get("message"); + final Map fieldMappings = + mappings.get("twitter"); + final GetFieldMappingsResponse.FieldMappingMetaData metaData1 = fieldMappings.get("message"); final String fullName = metaData1.fullName(); final Map source = metaData1.sourceAsMap(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetFieldMappingsResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetFieldMappingsResponseTests.java new file mode 100644 index 0000000000000..aa8ce3bb6c098 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/indices/GetFieldMappingsResponseTests.java @@ -0,0 +1,92 @@ +/* + * 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.indices; + +import org.elasticsearch.client.indices.GetFieldMappingsResponse.FieldMappingMetaData; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester; + +public class GetFieldMappingsResponseTests extends ESTestCase { + + public void testFromXContent() throws IOException { + xContentTester( + this::createParser, + GetFieldMappingsResponseTests::createTestInstance, + GetFieldMappingsResponseTests::toXContent, + GetFieldMappingsResponse::fromXContent) + .supportsUnknownFields(true) + .randomFieldsExcludeFilter(getRandomFieldsExcludeFilter()) + .test(); + } + + Predicate getRandomFieldsExcludeFilter() { + // allow random fields at the level of `index` and `index.mappings.field` + // otherwise random field could be evaluated as index name or type name + return s -> false == (s.matches("(?[^.]+)") + || s.matches("(?[^.]+)\\.mappings\\.(?[^.]+)")); + } + + static GetFieldMappingsResponse createTestInstance() { + Map> mappings = new HashMap<>(); + // if mappings is empty, means that fields are not found + if (randomBoolean()) { + int indices = randomInt(10); + for (int i = 0; i < indices; i++) { + Map fieldMappings = new HashMap<>(); + int fields = randomInt(10); + for (int k = 0; k < fields; k++) { + final String mapping = randomBoolean() ? "{\"type\":\"string\"}" : "{\"type\":\"keyword\"}"; + final String fieldName = randomAlphaOfLength(8); + FieldMappingMetaData metaData = new FieldMappingMetaData(fieldName, new BytesArray(mapping)); + fieldMappings.put(fieldName, metaData); + } + mappings.put(randomAlphaOfLength(8), fieldMappings); + } + } + return new GetFieldMappingsResponse(mappings); + } + + // As the client class GetFieldMappingsResponse doesn't have toXContent method, adding this method here only for the test + static void toXContent(GetFieldMappingsResponse response, XContentBuilder builder) throws IOException { + builder.startObject(); + for (Map.Entry> indexEntry : response.mappings().entrySet()) { + builder.startObject(indexEntry.getKey()); + builder.startObject("mappings"); + Map mappings = null; + for (Map.Entry fieldEntry : indexEntry.getValue().entrySet()) { + builder.startObject(fieldEntry.getKey()); + builder.field("full_name", fieldEntry.getValue().fullName()); + builder.field("mapping", fieldEntry.getValue().sourceAsMap()); + builder.endObject(); + } + builder.endObject(); + builder.endObject(); + } + builder.endObject(); + } +} diff --git a/docs/java-rest/high-level/indices/get_field_mappings.asciidoc b/docs/java-rest/high-level/indices/get_field_mappings.asciidoc index 0e965f8b6a3b1..2186c1b1ed4e6 100644 --- a/docs/java-rest/high-level/indices/get_field_mappings.asciidoc +++ b/docs/java-rest/high-level/indices/get_field_mappings.asciidoc @@ -18,8 +18,7 @@ include-tagged::{doc-tests-file}[{api}-request] -------------------------------------------------- <1> An empty request <2> Setting the indices to fetch mapping for -<3> The types to be returned -<4> The fields to be returned +<3> The fields to be returned ==== Optional arguments The following arguments can also optionally be provided: @@ -53,7 +52,7 @@ executed operation as follows: include-tagged::{doc-tests-file}[{api}-response] -------------------------------------------------- <1> Returning all requested indices fields' mappings -<2> Retrieving the mappings for a particular index and type +<2> Retrieving the mappings for a particular index <3> Getting the mappings metadata for the `message` field <4> Getting the full name of the field <5> Getting the mapping source of the field diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsRequest.java index 819d2de999ccf..dde7c13e2c01a 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsRequest.java @@ -29,7 +29,12 @@ import java.io.IOException; -/** Request the mappings of specific fields */ +/** + * Request the mappings of specific fields + * + * Note: there is a new class with the same name for the Java HLRC that uses a typeless format. + * Any changes done to this class should go to that client class as well. + */ public class GetFieldMappingsRequest extends ActionRequest implements IndicesRequest.Replaceable { protected boolean local = false; diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java index 2c07ebc68d0ef..3581d8f632f39 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/GetFieldMappingsResponse.java @@ -49,7 +49,12 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; -/** Response object for {@link GetFieldMappingsRequest} API */ +/** + * Response object for {@link GetFieldMappingsRequest} API + * + * Note: there is a new class with the same name for the Java HLRC that uses a typeless format. + * Any changes done to this class should go to that client class as well. + */ public class GetFieldMappingsResponse extends ActionResponse implements ToXContentObject { private static final ParseField MAPPINGS = new ParseField("mappings"); diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java index f3a73fa29fd98..86292cc81aee9 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingAction.java @@ -19,12 +19,14 @@ package org.elasticsearch.rest.action.admin.indices; +import org.apache.logging.log4j.LogManager; import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsResponse.FieldMappingMetaData; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.BaseRestHandler; @@ -43,6 +45,13 @@ import static org.elasticsearch.rest.RestStatus.OK; public class RestGetFieldMappingAction extends BaseRestHandler { + + private static final DeprecationLogger deprecationLogger = new DeprecationLogger( + LogManager.getLogger(RestGetFieldMappingAction.class)); + public static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in get field mapping " + + "requests is deprecated. The parameter include_type_name should be provided and set to false to be " + + "compatible with the next major version."; + public RestGetFieldMappingAction(Settings settings, RestController controller) { super(settings); controller.registerHandler(GET, "/_mapping/field/{fields}", this); @@ -63,10 +72,11 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC final String[] types = request.paramAsStringArrayOrEmptyIfAll("type"); final String[] fields = Strings.splitStringByCommaToArray(request.param("fields")); - boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, true); - if (includeTypeName == false && types.length > 0) { - throw new IllegalArgumentException("Cannot set include_type_name=false and specify" + - " types at the same time."); + final boolean includeTypeName = request.paramAsBoolean(INCLUDE_TYPE_NAME_PARAMETER, DEFAULT_INCLUDE_TYPE_NAME_POLICY); + if (includeTypeName) { + deprecationLogger.deprecatedAndMaybeLog("get_field_mapping_with_types", TYPES_DEPRECATION_MESSAGE); + } else if (types.length > 0) { + throw new IllegalArgumentException("Cannot set include_type_name=false and specify types at the same time."); } GetFieldMappingsRequest getMappingsRequest = new GetFieldMappingsRequest(); diff --git a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionTests.java index dcf4237ae07bf..e46c295610739 100644 --- a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionTests.java +++ b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestGetFieldMappingActionTests.java @@ -40,6 +40,24 @@ public void setUpAction() { new RestGetFieldMappingAction(Settings.EMPTY, controller()); } + public void testIncludeTypeName() { + RestRequest deprecatedRequest = new FakeRestRequest.Builder(xContentRegistry()) + .withMethod(RestRequest.Method.GET) + .withPath("some_index/some_type/_mapping/field/some_field") + .build(); + dispatchRequest(deprecatedRequest); + assertWarnings(RestGetFieldMappingAction.TYPES_DEPRECATION_MESSAGE); + + Map params = new HashMap<>(); + params.put(INCLUDE_TYPE_NAME_PARAMETER, "false"); + RestRequest validRequest = new FakeRestRequest.Builder(xContentRegistry()) + .withMethod(RestRequest.Method.GET) + .withPath("some_index/_mapping/field/some_field") + .withParams(params) + .build(); + dispatchRequest(validRequest); + } + public void testTypeInPath() { // Test that specifying a type while setting include_type_name to false // results in an illegal argument exception.