Add get field mappings to High Level REST API Client (#31423)

Add get field mappings to High Level REST API Client
Relates to #27205
This commit is contained in:
Vladimir Dolzhenko 2018-06-23 09:39:17 +02:00 committed by Colin Goodheart-Smithe
parent 54d1b9eb6f
commit 27bd68862a
No known key found for this signature in database
GPG Key ID: F975E7BDD739B3C7
9 changed files with 562 additions and 8 deletions

View File

@ -37,6 +37,8 @@ import org.elasticsearch.action.admin.indices.flush.SyncedFlushRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse;
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.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.mapping.put.PutMappingRequest;
@ -188,6 +190,35 @@ public final class IndicesClient {
GetMappingsResponse::fromXContent, listener, emptySet());
}
/**
* Retrieves the field mappings on an index or indices using the Get Field Mapping API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html">
* Get Field Mapping API on elastic.co</a>
* @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, RequestConverters::getFieldMapping, options,
GetFieldMappingsResponse::fromXContent, emptySet());
}
/**
* Asynchronously retrieves the field mappings on an index on indices using the Get Field Mapping API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html">
* Get Field Mapping API on elastic.co</a>
* @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<GetFieldMappingsResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(getFieldMappingsRequest, RequestConverters::getFieldMapping, options,
GetFieldMappingsResponse::fromXContent, listener, emptySet());
}
/**
* Updates aliases using the Index Aliases API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html">

View File

@ -50,6 +50,7 @@ import org.elasticsearch.action.admin.indices.flush.FlushRequest;
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.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
@ -230,6 +231,25 @@ final class RequestConverters {
return request;
}
static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRequest) throws IOException {
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();
String endpoint = new EndpointBuilder().addCommaSeparatedPathParts(indices)
.addPathPartAsIs("_mapping").addCommaSeparatedPathParts(types)
.addPathPartAsIs("field").addCommaSeparatedPathParts(fields)
.build();
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
Params parameters = new Params(request);
parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions());
parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults());
parameters.withLocal(getFieldMappingsRequest.local());
return request;
}
static Request refresh(RefreshRequest refreshRequest) {
String[] indices = refreshRequest.indices() == null ? Strings.EMPTY_ARRAY : refreshRequest.indices();
Request request = new Request(HttpPost.METHOD_NAME, endpoint(indices, "_refresh"));

View File

@ -43,6 +43,8 @@ import org.elasticsearch.action.admin.indices.flush.SyncedFlushRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse;
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.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.mapping.put.PutMappingRequest;
@ -74,6 +76,7 @@ import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
@ -378,6 +381,41 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
assertThat(mappings, equalTo(expected));
}
public void testGetFieldMapping() throws IOException {
String indexName = "test";
createIndex(indexName, Settings.EMPTY);
PutMappingRequest putMappingRequest = new PutMappingRequest(indexName);
putMappingRequest.type("_doc");
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());
GetFieldMappingsRequest getFieldMappingsRequest = new GetFieldMappingsRequest()
.indices(indexName)
.types("_doc")
.fields("field");
GetFieldMappingsResponse getFieldMappingsResponse =
execute(getFieldMappingsRequest,
highLevelClient().indices()::getFieldMapping,
highLevelClient().indices()::getFieldMappingAsync);
final Map<String, GetFieldMappingsResponse.FieldMappingMetaData> fieldMappingMap =
getFieldMappingsResponse.mappings().get(indexName).get("_doc");
final GetFieldMappingsResponse.FieldMappingMetaData metaData =
new GetFieldMappingsResponse.FieldMappingMetaData("field",
new BytesArray("{\"field\":{\"type\":\"text\"}}"));
assertThat(fieldMappingMap, equalTo(Collections.singletonMap("field", metaData)));
}
public void testDeleteIndex() throws IOException {
{
// Delete index if exists

View File

@ -52,6 +52,7 @@ import org.elasticsearch.action.admin.indices.flush.FlushRequest;
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.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
@ -457,6 +458,61 @@ public class RequestConvertersTests extends ESTestCase {
assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod()));
}
public void testGetFieldMapping() throws IOException {
GetFieldMappingsRequest getFieldMappingsRequest = new GetFieldMappingsRequest();
String[] indices = Strings.EMPTY_ARRAY;
if (randomBoolean()) {
indices = randomIndicesNames(0, 5);
getFieldMappingsRequest.indices(indices);
} else if (randomBoolean()) {
getFieldMappingsRequest.indices((String[]) null);
}
String type = null;
if (randomBoolean()) {
type = randomAlphaOfLengthBetween(3, 10);
getFieldMappingsRequest.types(type);
} else if (randomBoolean()) {
getFieldMappingsRequest.types((String[]) null);
}
String[] fields = null;
if (randomBoolean()) {
fields = new String[randomIntBetween(1, 5)];
for (int i = 0; i < fields.length; i++) {
fields[i] = randomAlphaOfLengthBetween(3, 10);
}
getFieldMappingsRequest.fields(fields);
} else if (randomBoolean()) {
getFieldMappingsRequest.fields((String[]) null);
}
Map<String, String> expectedParams = new HashMap<>();
setRandomIndicesOptions(getFieldMappingsRequest::indicesOptions, getFieldMappingsRequest::indicesOptions, expectedParams);
setRandomLocal(getFieldMappingsRequest::local, expectedParams);
Request request = RequestConverters.getFieldMapping(getFieldMappingsRequest);
StringJoiner endpoint = new StringJoiner("/", "/", "");
String index = String.join(",", indices);
if (Strings.hasLength(index)) {
endpoint.add(index);
}
endpoint.add("_mapping");
if (type != null) {
endpoint.add(type);
}
endpoint.add("field");
if (fields != null) {
endpoint.add(String.join(",", fields));
}
assertThat(endpoint.toString(), equalTo(request.getEndpoint()));
assertThat(expectedParams, equalTo(request.getParameters()));
assertThat(HttpGet.METHOD_NAME, equalTo(request.getMethod()));
}
public void testDeleteIndex() {
String[] indices = randomIndicesNames(0, 5);
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indices);
@ -2268,16 +2324,20 @@ public class RequestConvertersTests extends ESTestCase {
}
}
private static void setRandomLocal(MasterNodeReadRequest<?> request, Map<String, String> expectedParams) {
private static void setRandomLocal(Consumer<Boolean> setter, Map<String, String> expectedParams) {
if (randomBoolean()) {
boolean local = randomBoolean();
request.local(local);
setter.accept(local);
if (local) {
expectedParams.put("local", String.valueOf(local));
}
}
}
private static void setRandomLocal(MasterNodeReadRequest<?> request, Map<String, String> expectedParams) {
setRandomLocal(request::local, expectedParams);
}
private static void setRandomTimeout(Consumer<String> setter, TimeValue defaultTimeout, Map<String, String> expectedParams) {
if (randomBoolean()) {
String timeout = randomTimeValue();

View File

@ -41,6 +41,8 @@ import org.elasticsearch.action.admin.indices.flush.SyncedFlushRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse;
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.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.mapping.put.PutMappingRequest;
@ -703,6 +705,110 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
}
}
public void testGetFieldMapping() throws IOException, InterruptedException {
RestHighLevelClient client = highLevelClient();
{
CreateIndexResponse createIndexResponse = client.indices().create(new CreateIndexRequest("twitter"), RequestOptions.DEFAULT);
assertTrue(createIndexResponse.isAcknowledged());
PutMappingRequest request = new PutMappingRequest("twitter");
request.type("tweet");
request.source(
"{\n" +
" \"properties\": {\n" +
" \"message\": {\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"timestamp\": {\n" +
" \"type\": \"date\"\n" +
" }\n" +
" }\n" +
"}", // <1>
XContentType.JSON);
PutMappingResponse putMappingResponse = client.indices().putMapping(request, RequestOptions.DEFAULT);
assertTrue(putMappingResponse.isAcknowledged());
}
// tag::get-field-mapping-request
GetFieldMappingsRequest request = new GetFieldMappingsRequest(); // <1>
request.indices("twitter"); // <2>
request.types("tweet"); // <3>
request.fields("message", "timestamp"); // <4>
// end::get-field-mapping-request
// tag::get-field-mapping-request-indicesOptions
request.indicesOptions(IndicesOptions.lenientExpandOpen()); // <1>
// end::get-field-mapping-request-indicesOptions
// tag::get-field-mapping-request-local
request.local(true); // <1>
// end::get-field-mapping-request-local
{
// tag::get-field-mapping-execute
GetFieldMappingsResponse response =
client.indices().getFieldMapping(request, RequestOptions.DEFAULT);
// end::get-field-mapping-execute
// tag::get-field-mapping-response
final Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetaData>>> mappings =
response.mappings();// <1>
final Map<String, GetFieldMappingsResponse.FieldMappingMetaData> typeMappings =
mappings.get("twitter").get("tweet"); // <2>
final GetFieldMappingsResponse.FieldMappingMetaData metaData =
typeMappings.get("message");// <3>
final String fullName = metaData.fullName();// <4>
final Map<String, Object> source = metaData.sourceAsMap(); // <5>
// end::get-field-mapping-response
}
{
// tag::get-field-mapping-execute-listener
ActionListener<GetFieldMappingsResponse> listener =
new ActionListener<GetFieldMappingsResponse>() {
@Override
public void onResponse(GetFieldMappingsResponse putMappingResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::get-field-mapping-execute-listener
// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
final ActionListener<GetFieldMappingsResponse> latchListener = new LatchedActionListener<>(listener, latch);
listener = ActionListener.wrap(r -> {
final Map<String, Map<String, Map<String, GetFieldMappingsResponse.FieldMappingMetaData>>> mappings =
r.mappings();
final Map<String, GetFieldMappingsResponse.FieldMappingMetaData> typeMappings =
mappings.get("twitter").get("tweet");
final GetFieldMappingsResponse.FieldMappingMetaData metaData1 = typeMappings.get("message");
final String fullName = metaData1.fullName();
final Map<String, Object> source = metaData1.sourceAsMap();
latchListener.onResponse(r);
}, e -> {
latchListener.onFailure(e);
fail("should not fail");
});
// tag::get-field-mapping-execute-async
client.indices().getFieldMappingAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::get-field-mapping-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
public void testOpenIndex() throws Exception {
RestHighLevelClient client = highLevelClient();

View File

@ -0,0 +1,86 @@
[[java-rest-high-get-field-mappings]]
=== Get Field Mappings API
[[java-rest-high-get-field-mappings-request]]
==== Get Field Mappings Request
A `GetFieldMappingsRequest` can have an optional list of indices, optional list of types and the list of fields:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-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
==== Optional arguments
The following arguments can also optionally be provided:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-request-indicesOptions]
--------------------------------------------------
<1> Setting `IndicesOptions` controls how unavailable indices are resolved and
how wildcard expressions are expanded
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-request-local]
--------------------------------------------------
<1> The `local` flag (defaults to `false`) controls whether the aliases need
to be looked up in the local cluster state or in the cluster state held by
the elected master node
[[java-rest-high-get-field-mappings-sync]]
==== Synchronous Execution
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-execute]
--------------------------------------------------
[[java-rest-high-get-field-mapping-async]]
==== Asynchronous Execution
The asynchronous execution of a get mappings request requires both the
`GetFieldMappingsRequest` instance and an `ActionListener` instance to be passed to
the asynchronous method:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-execute-async]
--------------------------------------------------
<1> The `GetFieldMappingsRequest` to execute and the `ActionListener` to use when the execution completes
The asynchronous method does not block and returns immediately. Once it is
completed the `ActionListener` is called back using the `onResponse` method if
the execution successfully completed or using the `onFailure` method if it
failed.
A typical listener for `GetMappingsResponse` looks like:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-execute-listener]
--------------------------------------------------
<1> Called when the execution is successfully completed. The response is provided as an argument
<2> Called in case of failure. The raised exception is provided as an argument
[[java-rest-high-get-field-mapping-response]]
==== Get Field Mappings Response
The returned `GetFieldMappingsResponse` allows to retrieve information about the
executed operation as follows:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[get-field-mapping-response]
--------------------------------------------------
<1> Returning all requested indices fields' mappings
<2> Retrieving the mappings for a particular index and type
<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

View File

@ -77,6 +77,7 @@ Index Management::
Mapping Management::
* <<java-rest-high-put-mapping>>
* <<java-rest-high-get-field-mappings>>
Alias Management::
* <<java-rest-high-update-aliases>>
@ -98,6 +99,7 @@ include::indices/force_merge.asciidoc[]
include::indices/rollover.asciidoc[]
include::indices/put_mapping.asciidoc[]
include::indices/get_mappings.asciidoc[]
include::indices/get_field_mappings.asciidoc[]
include::indices/update_aliases.asciidoc[]
include::indices/exists_alias.asciidoc[]
include::indices/get_alias.asciidoc[]

View File

@ -20,13 +20,17 @@
package org.elasticsearch.action.admin.indices.mapping.get;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.ParseField;
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.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentFragment;
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;
@ -34,13 +38,45 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
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 extends ActionResponse implements ToXContentFragment {
private static final ParseField MAPPINGS = new ParseField("mappings");
private static final ObjectParser<Map<String, Map<String, FieldMappingMetaData>>, String> PARSER =
new ObjectParser<>(MAPPINGS.getPreferredName(), true, HashMap::new);
static {
PARSER.declareField((p, typeMappings, index) -> {
p.nextToken();
while (p.currentToken() == XContentParser.Token.FIELD_NAME) {
final String typeName = p.currentName();
if (p.nextToken() == XContentParser.Token.START_OBJECT) {
final Map<String, FieldMappingMetaData> typeMapping = new HashMap<>();
typeMappings.put(typeName, typeMapping);
while (p.nextToken() == XContentParser.Token.FIELD_NAME) {
final String fieldName = p.currentName();
final FieldMappingMetaData fieldMappingMetaData = FieldMappingMetaData.fromXContent(p);
typeMapping.put(fieldName, fieldMappingMetaData);
}
} else {
p.skipChildren();
}
p.nextToken();
}
}, MAPPINGS, ObjectParser.ValueType.OBJECT);
}
private Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = emptyMap();
GetFieldMappingsResponse(Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings) {
@ -77,7 +113,7 @@ public class GetFieldMappingsResponse extends ActionResponse implements ToXConte
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
for (Map.Entry<String, Map<String, Map<String, FieldMappingMetaData>>> indexEntry : mappings.entrySet()) {
builder.startObject(indexEntry.getKey());
builder.startObject("mappings");
builder.startObject(MAPPINGS.getPreferredName());
for (Map.Entry<String, Map<String, FieldMappingMetaData>> typeEntry : indexEntry.getValue().entrySet()) {
builder.startObject(typeEntry.getKey());
for (Map.Entry<String, FieldMappingMetaData> fieldEntry : typeEntry.getValue().entrySet()) {
@ -93,9 +129,46 @@ public class GetFieldMappingsResponse extends ActionResponse implements ToXConte
return builder;
}
public static GetFieldMappingsResponse fromXContent(XContentParser parser) throws IOException {
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
final Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = new HashMap<>();
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
while (parser.currentToken() == XContentParser.Token.FIELD_NAME) {
final String index = parser.currentName();
final Map<String, Map<String, FieldMappingMetaData>> typeMappings = PARSER.parse(parser, index);
mappings.put(index, typeMappings);
parser.nextToken();
}
}
return new GetFieldMappingsResponse(mappings);
}
public static class FieldMappingMetaData implements ToXContentFragment {
public static final FieldMappingMetaData NULL = new FieldMappingMetaData("", BytesArray.EMPTY);
private static final ParseField FULL_NAME = new ParseField("full_name");
private static final ParseField MAPPING = new ParseField("mapping");
private static final ConstructingObjectParser<FieldMappingMetaData, String> 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;
@ -122,18 +195,41 @@ public class GetFieldMappingsResponse extends ActionResponse implements ToXConte
return source;
}
public static FieldMappingMetaData fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("full_name", fullName);
builder.field(FULL_NAME.getPreferredName(), fullName);
if (params.paramAsBoolean("pretty", false)) {
builder.field("mapping", sourceAsMap());
} else {
try (InputStream stream = source.streamInput()) {
builder.rawField("mapping", stream, XContentType.JSON);
builder.rawField(MAPPING.getPreferredName(), stream, XContentType.JSON);
}
}
return builder;
}
@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
@ -178,4 +274,25 @@ public class GetFieldMappingsResponse extends ActionResponse implements ToXConte
}
}
}
@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);
}
}

View File

@ -23,16 +23,22 @@ import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsRespon
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
public class GetFieldMappingsResponseTests extends ESTestCase {
import static org.hamcrest.CoreMatchers.equalTo;
public void testSerialization() throws IOException {
public class GetFieldMappingsResponseTests extends AbstractStreamableXContentTestCase<GetFieldMappingsResponse> {
public void testManualSerialization() throws IOException {
Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = new HashMap<>();
FieldMappingMetaData fieldMappingMetaData = new FieldMappingMetaData("my field", new BytesArray("{}"));
mappings.put("index", Collections.singletonMap("type", Collections.singletonMap("field", fieldMappingMetaData)));
@ -49,4 +55,92 @@ public class GetFieldMappingsResponseTests extends ESTestCase {
}
}
}
public void testManualJunkedJson() throws Exception {
// in fact random fields could be evaluated as proper mapping, while proper junk in this case is arrays and values
final String json =
"{\"index1\":{\"mappings\":"
+ "{\"doctype0\":{\"field1\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}},"
+ "\"field0\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}}},"
// junk here
+ "\"junk1\": [\"field1\", {\"field2\":{}}],"
+ "\"junk2\": [{\"field3\":{}}],"
+ "\"junk3\": 42,"
+ "\"junk4\": \"Q\","
+ "\"doctype1\":{\"field1\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}},"
+ "\"field0\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}}}}},"
+ "\"index0\":{\"mappings\":"
+ "{\"doctype0\":{\"field1\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}},"
+ "\"field0\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}}},"
+ "\"doctype1\":{\"field1\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}},"
+ "\"field0\":{\"full_name\":\"my field\",\"mapping\":{\"type\":\"keyword\"}}}}}}";
final XContentParser parser = XContentType.JSON.xContent().createParser(xContentRegistry(),
LoggingDeprecationHandler.INSTANCE, json.getBytes("UTF-8"));
final GetFieldMappingsResponse response = GetFieldMappingsResponse.fromXContent(parser);
FieldMappingMetaData fieldMappingMetaData =
new FieldMappingMetaData("my field", new BytesArray("{\"type\":\"keyword\"}"));
Map<String, FieldMappingMetaData> fieldMapping = new HashMap<>();
fieldMapping.put("field0", fieldMappingMetaData);
fieldMapping.put("field1", fieldMappingMetaData);
Map<String, Map<String, FieldMappingMetaData>> typeMapping = new HashMap<>();
typeMapping.put("doctype0", fieldMapping);
typeMapping.put("doctype1", fieldMapping);
Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = new HashMap<>();
mappings.put("index0", typeMapping);
mappings.put("index1", typeMapping);
final Map<String, Map<String, Map<String, FieldMappingMetaData>>> responseMappings = response.mappings();
assertThat(responseMappings, equalTo(mappings));
}
@Override
protected GetFieldMappingsResponse doParseInstance(XContentParser parser) throws IOException {
return GetFieldMappingsResponse.fromXContent(parser);
}
@Override
protected GetFieldMappingsResponse createBlankInstance() {
return new GetFieldMappingsResponse();
}
@Override
protected GetFieldMappingsResponse createTestInstance() {
return new GetFieldMappingsResponse(randomMapping());
}
@Override
protected Predicate<String> getRandomFieldsExcludeFilter() {
// allow random fields at the level of `index` and `index.mappings.doctype.field`
// otherwise random field could be evaluated as index name or type name
return s -> false == (s.matches("(?<index>[^.]+)")
|| s.matches("(?<index>[^.]+)\\.mappings\\.(?<doctype>[^.]+)\\.(?<field>[^.]+)"));
}
private Map<String, Map<String, Map<String, FieldMappingMetaData>>> randomMapping() {
Map<String, Map<String, Map<String, FieldMappingMetaData>>> mappings = new HashMap<>();
int indices = randomInt(10);
for(int i = 0; i < indices; i++) {
final Map<String, Map<String, FieldMappingMetaData>> doctypesMappings = new HashMap<>();
int doctypes = randomInt(10);
for(int j = 0; j < doctypes; j++) {
Map<String, FieldMappingMetaData> fieldMappings = new HashMap<>();
int fields = randomInt(10);
for(int k = 0; k < fields; k++) {
final String mapping = randomBoolean() ? "{\"type\":\"string\"}" : "{\"type\":\"keyword\"}";
FieldMappingMetaData metaData =
new FieldMappingMetaData("my field", new BytesArray(mapping));
fieldMappings.put("field" + k, metaData);
}
doctypesMappings.put("doctype" + j, fieldMappings);
}
mappings.put("index" + i, doctypesMappings);
}
return mappings;
}
}