diff options
author | Martijn van Groningen <martijn.v.groningen@gmail.com> | 2017-11-10 07:19:01 +0100 |
---|---|---|
committer | Martijn van Groningen <martijn.v.groningen@gmail.com> | 2018-01-16 17:27:02 +0100 |
commit | 853f7e878031d43267b7365f3b2b4beec513aa10 (patch) | |
tree | 914234f3dfbfd5b9d4fd25d7dd1c9a29612c1482 | |
parent | d4ac0026fc7dacbf66c184327b8e39b15d0a2d56 (diff) |
Added multi get api to the high level rest client.
Relates to #27205
10 files changed, 467 insertions, 47 deletions
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java index dd08179cf6..d35db1c637 100755 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java @@ -35,6 +35,7 @@ import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.ClearScrollRequest; import org.elasticsearch.action.search.MultiSearchRequest; @@ -312,6 +313,15 @@ public final class Request { return new Request(HttpGet.METHOD_NAME, endpoint, parameters.getParams(), null); } + static Request multiGet(MultiGetRequest multiGetRequest) throws IOException { + Params parameters = Params.builder(); + parameters.withPreference(multiGetRequest.preference()); + parameters.withRealtime(multiGetRequest.realtime()); + parameters.withRefresh(multiGetRequest.refresh()); + HttpEntity entity = createEntity(multiGetRequest, REQUEST_BODY_CONTENT_TYPE); + return new Request(HttpGet.METHOD_NAME, "/_mget", parameters.getParams(), entity); + } + static Request index(IndexRequest indexRequest) { String method = Strings.hasLength(indexRequest.id()) ? HttpPut.METHOD_NAME : HttpPost.METHOD_NAME; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index ca244eee88..cad7449c68 100755 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -34,6 +34,8 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.main.MainRequest; @@ -290,6 +292,25 @@ public class RestHighLevelClient implements Closeable { } /** + * Retrieves multiple documents by id using the Multi Get API + * + * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html">Multi Get API on elastic.co</a> + */ + public final MultiGetResponse multiGet(MultiGetRequest multiGetRequest, Header... headers) throws IOException { + return performRequestAndParseEntity(multiGetRequest, Request::multiGet, MultiGetResponse::fromXContent, singleton(404), headers); + } + + /** + * Asynchronously retrieves multiple documents by id using the Multi Get API + * + * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html">Multi Get API on elastic.co</a> + */ + public void multiGetAsync(MultiGetRequest multiGetRequest, ActionListener<MultiGetResponse> listener, Header... headers) { + performRequestAsyncAndParseEntity(multiGetRequest, Request::multiGet, MultiGetResponse::fromXContent, listener, + singleton(404), headers); + } + + /** * Checks for the existence of a document. Returns true if it exists, false otherwise * * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html">Get API on elastic.co</a> diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java index e36c445082..14d29ddd9e 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java @@ -33,6 +33,8 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetRequest; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.update.UpdateRequest; @@ -238,6 +240,64 @@ public class CrudIT extends ESRestHighLevelClientTestCase { } } + public void testMultiGet() throws IOException { + { + MultiGetRequest multiGetRequest = new MultiGetRequest(); + multiGetRequest.add("index", "type", "id1"); + multiGetRequest.add("index", "type", "id2"); + MultiGetResponse response = execute(multiGetRequest, highLevelClient()::multiGet, highLevelClient()::multiGetAsync); + assertEquals(2, response.getResponses().length); + + assertTrue(response.getResponses()[0].isFailed()); + assertNull(response.getResponses()[0].getResponse()); + assertEquals("id1", response.getResponses()[0].getFailure().getId()); + assertEquals("type", response.getResponses()[0].getFailure().getType()); + assertEquals("index", response.getResponses()[0].getFailure().getIndex()); + assertEquals("Elasticsearch exception [type=index_not_found_exception, reason=no such index]", + response.getResponses()[0].getFailure().getFailure().getMessage()); + + assertTrue(response.getResponses()[1].isFailed()); + assertNull(response.getResponses()[1].getResponse()); + assertEquals("id2", response.getResponses()[1].getId()); + assertEquals("type", response.getResponses()[1].getType()); + assertEquals("index", response.getResponses()[1].getIndex()); + assertEquals("Elasticsearch exception [type=index_not_found_exception, reason=no such index]", + response.getResponses()[1].getFailure().getFailure().getMessage()); + } + + String document = "{\"field\":\"value1\"}"; + StringEntity stringEntity = new StringEntity(document, ContentType.APPLICATION_JSON); + Response r = client().performRequest("PUT", "/index/type/id1", Collections.singletonMap("refresh", "true"), stringEntity); + assertEquals(201, r.getStatusLine().getStatusCode()); + + document = "{\"field\":\"value2\"}"; + stringEntity = new StringEntity(document, ContentType.APPLICATION_JSON); + r = client().performRequest("PUT", "/index/type/id2", Collections.singletonMap("refresh", "true"), stringEntity); + assertEquals(201, r.getStatusLine().getStatusCode()); + + { + MultiGetRequest multiGetRequest = new MultiGetRequest(); + multiGetRequest.add("index", "type", "id1"); + multiGetRequest.add("index", "type", "id2"); + MultiGetResponse response = execute(multiGetRequest, highLevelClient()::multiGet, highLevelClient()::multiGetAsync); + assertEquals(2, response.getResponses().length); + + assertFalse(response.getResponses()[0].isFailed()); + assertNull(response.getResponses()[0].getFailure()); + assertEquals("id1", response.getResponses()[0].getId()); + assertEquals("type", response.getResponses()[0].getType()); + assertEquals("index", response.getResponses()[0].getIndex()); + assertEquals(Collections.singletonMap("field", "value1"), response.getResponses()[0].getResponse().getSource()); + + assertFalse(response.getResponses()[1].isFailed()); + assertNull(response.getResponses()[1].getFailure()); + assertEquals("id2", response.getResponses()[1].getId()); + assertEquals("type", response.getResponses()[1].getType()); + assertEquals("index", response.getResponses()[1].getIndex()); + assertEquals(Collections.singletonMap("field", "value2"), response.getResponses()[1].getResponse().getSource()); + } + } + public void testIndex() throws IOException { final XContentType xContentType = randomFrom(XContentType.values()); { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java index 019162bae3..acb27fff7e 100755 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java @@ -32,6 +32,7 @@ import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.ClearScrollRequest; import org.elasticsearch.action.search.MultiSearchRequest; @@ -147,6 +148,59 @@ public class RequestTests extends ESTestCase { getAndExistsTest(Request::get, "GET"); } + public void testMultiGet() throws IOException { + Map<String, String> expectedParams = new HashMap<>(); + MultiGetRequest multiGetRequest = new MultiGetRequest(); + if (randomBoolean()) { + String preference = randomAlphaOfLength(4); + multiGetRequest.preference(preference); + expectedParams.put("preference", preference); + } + if (randomBoolean()) { + multiGetRequest.realtime(randomBoolean()); + if (multiGetRequest.realtime() == false) { + expectedParams.put("realtime", "false"); + } + } + if (randomBoolean()) { + multiGetRequest.refresh(randomBoolean()); + if (multiGetRequest.refresh()) { + expectedParams.put("refresh", "true"); + } + } + + int numberOfRequests = randomIntBetween(0, 32); + for (int i = 0; i < numberOfRequests; i++) { + MultiGetRequest.Item item = + new MultiGetRequest.Item(randomAlphaOfLength(4), randomAlphaOfLength(4), randomAlphaOfLength(4)); + if (randomBoolean()) { + item.routing(randomAlphaOfLength(4)); + } + if (randomBoolean()) { + item.parent(randomAlphaOfLength(4)); + } + if (randomBoolean()) { + item.storedFields(generateRandomStringArray(16, 8, false)); + } + if (randomBoolean()) { + item.version(randomNonNegativeLong()); + } + if (randomBoolean()) { + item.versionType(randomFrom(VersionType.values())); + } + if (randomBoolean()) { + randomizeFetchSourceContextParams(item::fetchSourceContext, new HashMap<>()); + } + multiGetRequest.add(item); + } + + Request request = Request.multiGet(multiGetRequest); + assertEquals("GET", request.getMethod()); + assertEquals("/_mget", request.getEndpoint()); + assertEquals(expectedParams, request.getParameters()); + assertToXContentBody(multiGetRequest, request.getEntity()); + } + public void testDelete() { String index = randomAlphaOfLengthBetween(3, 10); String type = randomAlphaOfLengthBetween(3, 10); diff --git a/core/src/test/java/org/elasticsearch/action/get/MultiGetResponseTests.java b/core/src/test/java/org/elasticsearch/action/get/MultiGetResponseTests.java new file mode 100644 index 0000000000..82638870ee --- /dev/null +++ b/core/src/test/java/org/elasticsearch/action/get/MultiGetResponseTests.java @@ -0,0 +1,83 @@ +/* + * 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.get; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.get.GetResult; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +import static org.elasticsearch.test.XContentTestUtils.insertRandomFields; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +public class MultiGetResponseTests extends ESTestCase { + + public void testFromXContent() throws IOException { + for (int runs = 0; runs < 20; runs++) { + MultiGetResponse expected = createTestInstance(); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference shuffled = toShuffledXContent(expected, xContentType, ToXContent.EMPTY_PARAMS, false); + + XContentParser parser = createParser(XContentFactory.xContent(xContentType), shuffled); + MultiGetResponse parsed = MultiGetResponse.fromXContent(parser); + assertNull(parser.nextToken()); + assertNotSame(expected, parsed); + + assertThat(parsed.getResponses().length, equalTo(expected.getResponses().length)); + for (int i = 0; i < expected.getResponses().length; i++) { + MultiGetItemResponse expectedItem = expected.getResponses()[i]; + MultiGetItemResponse actualItem = parsed.getResponses()[i]; + assertThat(actualItem.getIndex(), equalTo(expectedItem.getIndex())); + assertThat(actualItem.getType(), equalTo(expectedItem.getType())); + assertThat(actualItem.getId(), equalTo(expectedItem.getId())); + if (expectedItem.isFailed()) { + assertThat(actualItem.isFailed(), is(true)); + assertThat(actualItem.getFailure().getMessage(), containsString(expectedItem.getFailure().getMessage())); + } else { + assertThat(actualItem.isFailed(), is(false)); + assertThat(actualItem.getResponse(), equalTo(expectedItem.getResponse())); + } + } + } + } + + private static MultiGetResponse createTestInstance() { + MultiGetItemResponse[] items = new MultiGetItemResponse[randomIntBetween(0, 128)]; + for (int i = 0; i < items.length; i++) { + if (randomBoolean()) { + items[i] = new MultiGetItemResponse(new GetResponse(new GetResult( + randomAlphaOfLength(4), randomAlphaOfLength(4), randomAlphaOfLength(4), randomNonNegativeLong(), + true, null, null + )), null); + } else { + items[i] = new MultiGetItemResponse(null, new MultiGetResponse.Failure(randomAlphaOfLength(4), + randomAlphaOfLength(4), randomAlphaOfLength(4), new RuntimeException(randomAlphaOfLength(4)))); + } + } + return new MultiGetResponse(items); + } + +} diff --git a/server/src/main/java/org/elasticsearch/action/get/MultiGetRequest.java b/server/src/main/java/org/elasticsearch/action/get/MultiGetRequest.java index 48e3f5e81b..a7b63da897 100644 --- a/server/src/main/java/org/elasticsearch/action/get/MultiGetRequest.java +++ b/server/src/main/java/org/elasticsearch/action/get/MultiGetRequest.java @@ -35,7 +35,10 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.index.VersionType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; @@ -47,8 +50,10 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; -public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetRequest.Item>, CompositeIndicesRequest, RealtimeRequest { +public class MultiGetRequest extends ActionRequest + implements Iterable<MultiGetRequest.Item>, CompositeIndicesRequest, RealtimeRequest, ToXContentObject { + private static final ParseField DOCS = new ParseField("docs"); private static final ParseField INDEX = new ParseField("_index"); private static final ParseField TYPE = new ParseField("_type"); private static final ParseField ID = new ParseField("_id"); @@ -63,7 +68,8 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR /** * A single get item. */ - public static class Item implements Streamable, IndicesRequest { + public static class Item implements Streamable, IndicesRequest, ToXContentObject { + private String index; private String type; private String id; @@ -221,6 +227,22 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR } @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(INDEX.getPreferredName(), index); + builder.field(TYPE.getPreferredName(), type); + builder.field(ID.getPreferredName(), id); + builder.field(ROUTING.getPreferredName(), routing); + builder.field(PARENT.getPreferredName(), parent); + builder.field(STORED_FIELDS.getPreferredName(), storedFields); + builder.field(VERSION.getPreferredName(), version); + builder.field(VERSION_TYPE.getPreferredName(), VersionType.toString(versionType)); + builder.field(SOURCE.getPreferredName(), fetchSourceContext); + builder.endObject(); + return builder; + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Item)) return false; @@ -254,6 +276,11 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR result = 31 * result + (fetchSourceContext != null ? fetchSourceContext.hashCode() : 0); return result; } + + public String toString() { + return Strings.toString(this); + } + } String preference; @@ -330,20 +357,20 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR public MultiGetRequest add(@Nullable String defaultIndex, @Nullable String defaultType, @Nullable String[] defaultFields, @Nullable FetchSourceContext defaultFetchSource, @Nullable String defaultRouting, XContentParser parser, boolean allowExplicitIndex) throws IOException { - XContentParser.Token token; + Token token; String currentFieldName = null; - if ((token = parser.nextToken()) != XContentParser.Token.START_OBJECT) { + if ((token = parser.nextToken()) != Token.START_OBJECT) { final String message = String.format( Locale.ROOT, "unexpected token [%s], expected [%s]", token, - XContentParser.Token.START_OBJECT); + Token.START_OBJECT); throw new ParsingException(parser.getTokenLocation(), message); } - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { + while ((token = parser.nextToken()) != Token.END_OBJECT) { + if (token == Token.FIELD_NAME) { currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_ARRAY) { + } else if (token == Token.START_ARRAY) { if ("docs".equals(currentFieldName)) { parseDocuments(parser, this.items, defaultIndex, defaultType, defaultFields, defaultFetchSource, defaultRouting, allowExplicitIndex); } else if ("ids".equals(currentFieldName)) { @@ -361,19 +388,19 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR Locale.ROOT, "unexpected token [%s], expected [%s] or [%s]", token, - XContentParser.Token.FIELD_NAME, - XContentParser.Token.START_ARRAY); + Token.FIELD_NAME, + Token.START_ARRAY); throw new ParsingException(parser.getTokenLocation(), message); } } return this; } - public static void parseDocuments(XContentParser parser, List<Item> items, @Nullable String defaultIndex, @Nullable String defaultType, @Nullable String[] defaultFields, @Nullable FetchSourceContext defaultFetchSource, @Nullable String defaultRouting, boolean allowExplicitIndex) throws IOException { + private static void parseDocuments(XContentParser parser, List<Item> items, @Nullable String defaultIndex, @Nullable String defaultType, @Nullable String[] defaultFields, @Nullable FetchSourceContext defaultFetchSource, @Nullable String defaultRouting, boolean allowExplicitIndex) throws IOException { String currentFieldName = null; - XContentParser.Token token; - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - if (token != XContentParser.Token.START_OBJECT) { + Token token; + while ((token = parser.nextToken()) != Token.END_ARRAY) { + if (token != Token.START_OBJECT) { throw new IllegalArgumentException("docs array element should include an object"); } String index = defaultIndex; @@ -387,8 +414,8 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR FetchSourceContext fetchSourceContext = FetchSourceContext.FETCH_SOURCE; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { + while ((token = parser.nextToken()) != Token.END_OBJECT) { + if (token == Token.FIELD_NAME) { currentFieldName = parser.currentName(); } else if (token.isValue()) { if (INDEX.match(currentFieldName)) { @@ -419,7 +446,7 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR if (parser.isBooleanValueLenient()) { fetchSourceContext = new FetchSourceContext(parser.booleanValue(), fetchSourceContext.includes(), fetchSourceContext.excludes()); - } else if (token == XContentParser.Token.VALUE_STRING) { + } else if (token == Token.VALUE_STRING) { fetchSourceContext = new FetchSourceContext(fetchSourceContext.fetchSource(), new String[]{parser.text()}, fetchSourceContext.excludes()); } else { @@ -428,30 +455,30 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR } else { throw new ElasticsearchParseException("failed to parse multi get request. unknown field [{}]", currentFieldName); } - } else if (token == XContentParser.Token.START_ARRAY) { + } else if (token == Token.START_ARRAY) { if (FIELDS.match(currentFieldName)) { throw new ParsingException(parser.getTokenLocation(), "Unsupported field [fields] used, expected [stored_fields] instead"); } else if (STORED_FIELDS.match(currentFieldName)) { storedFields = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + while ((token = parser.nextToken()) != Token.END_ARRAY) { storedFields.add(parser.text()); } } else if (SOURCE.match(currentFieldName)) { ArrayList<String> includes = new ArrayList<>(); - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + while ((token = parser.nextToken()) != Token.END_ARRAY) { includes.add(parser.text()); } fetchSourceContext = new FetchSourceContext(fetchSourceContext.fetchSource(), includes.toArray(Strings.EMPTY_ARRAY) , fetchSourceContext.excludes()); } - } else if (token == XContentParser.Token.START_OBJECT) { + } else if (token == Token.START_OBJECT) { if (SOURCE.match(currentFieldName)) { List<String> currentList = null, includes = null, excludes = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { + while ((token = parser.nextToken()) != Token.END_OBJECT) { + if (token == Token.FIELD_NAME) { currentFieldName = parser.currentName(); if ("includes".equals(currentFieldName) || "include".equals(currentFieldName)) { currentList = includes != null ? includes : (includes = new ArrayList<>(2)); @@ -460,8 +487,8 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR } else { throw new ElasticsearchParseException("source definition may not contain [{}]", parser.text()); } - } else if (token == XContentParser.Token.START_ARRAY) { - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + } else if (token == Token.START_ARRAY) { + while ((token = parser.nextToken()) != Token.END_ARRAY) { currentList.add(parser.text()); } } else if (token.isValue()) { @@ -488,13 +515,9 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR } } - public static void parseDocuments(XContentParser parser, List<Item> items) throws IOException { - parseDocuments(parser, items, null, null, null, null, null, true); - } - public static void parseIds(XContentParser parser, List<Item> items, @Nullable String defaultIndex, @Nullable String defaultType, @Nullable String[] defaultFields, @Nullable FetchSourceContext defaultFetchSource, @Nullable String defaultRouting) throws IOException { - XContentParser.Token token; - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + Token token; + while ((token = parser.nextToken()) != Token.END_ARRAY) { if (!token.isValue()) { throw new IllegalArgumentException("ids array element should only contain ids"); } @@ -537,4 +560,17 @@ public class MultiGetRequest extends ActionRequest implements Iterable<MultiGetR item.writeTo(out); } } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.startArray(DOCS.getPreferredName()); + for (Item item : items) { + builder.value(item); + } + builder.endArray(); + builder.endObject(); + return builder; + } + } diff --git a/server/src/main/java/org/elasticsearch/action/get/MultiGetResponse.java b/server/src/main/java/org/elasticsearch/action/get/MultiGetResponse.java index 93e4272bd9..9cd9f71a6c 100644 --- a/server/src/main/java/org/elasticsearch/action/get/MultiGetResponse.java +++ b/server/src/main/java/org/elasticsearch/action/get/MultiGetResponse.java @@ -21,29 +21,41 @@ package org.elasticsearch.action.get; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; +import org.elasticsearch.index.get.GetResult; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; public class MultiGetResponse extends ActionResponse implements Iterable<MultiGetItemResponse>, ToXContentObject { + private static final ParseField INDEX = new ParseField("_index"); + private static final ParseField TYPE = new ParseField("_type"); + private static final ParseField ID = new ParseField("_id"); + private static final ParseField ERROR = new ParseField("error"); + private static final ParseField DOCS = new ParseField("docs"); + /** * Represents a failure. */ - public static class Failure implements Streamable { + public static class Failure implements Streamable, ToXContentObject { + private String index; private String type; private String id; private Exception exception; Failure() { - } public Failure(String index, String type, String id, Exception exception) { @@ -103,6 +115,17 @@ public class MultiGetResponse extends ActionResponse implements Iterable<MultiGe out.writeException(exception); } + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(INDEX.getPreferredName(), index); + builder.field(TYPE.getPreferredName(), type); + builder.field(ID.getPreferredName(), id); + ElasticsearchException.generateFailureXContent(builder, params, exception, true); + builder.endObject(); + return builder; + } + public Exception getFailure() { return exception; } @@ -129,16 +152,11 @@ public class MultiGetResponse extends ActionResponse implements Iterable<MultiGe @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.startArray(Fields.DOCS); + builder.startArray(DOCS.getPreferredName()); for (MultiGetItemResponse response : responses) { if (response.isFailed()) { - builder.startObject(); Failure failure = response.getFailure(); - builder.field(Fields._INDEX, failure.getIndex()); - builder.field(Fields._TYPE, failure.getType()); - builder.field(Fields._ID, failure.getId()); - ElasticsearchException.generateFailureXContent(builder, params, failure.getFailure(), true); - builder.endObject(); + failure.toXContent(builder, params); } else { GetResponse getResponse = response.getResponse(); getResponse.toXContent(builder, params); @@ -149,11 +167,78 @@ public class MultiGetResponse extends ActionResponse implements Iterable<MultiGe return builder; } - static final class Fields { - static final String DOCS = "docs"; - static final String _INDEX = "_index"; - static final String _TYPE = "_type"; - static final String _ID = "_id"; + public static MultiGetResponse fromXContent(XContentParser parser) throws IOException { + String currentFieldName = null; + List<MultiGetItemResponse> items = new ArrayList<>(); + for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) { + switch (token) { + case FIELD_NAME: + currentFieldName = parser.currentName(); + break; + case START_ARRAY: + if (DOCS.getPreferredName().equals(currentFieldName)) { + for (token = parser.nextToken(); token != Token.END_ARRAY; token = parser.nextToken()) { + if (token == Token.START_OBJECT) { + items.add(parseItem(parser)); + } + } + } + break; + default: + // If unknown tokens are encounter then these should be ignored, because + // this is parsing logic on the client side. + break; + } + } + return new MultiGetResponse(items.toArray(new MultiGetItemResponse[0])); + } + + private static MultiGetItemResponse parseItem(XContentParser parser) throws IOException { + String currentFieldName = null; + String index = null; + String type = null; + String id = null; + ElasticsearchException exception = null; + GetResult getResult = null; + for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) { + switch (token) { + case FIELD_NAME: + currentFieldName = parser.currentName(); + if (INDEX.match(currentFieldName) == false && TYPE.match(currentFieldName) == false && + ID.match(currentFieldName) == false && ERROR.match(currentFieldName) == false) { + getResult = GetResult.fromXContentEmbedded(parser, index, type, id); + } + break; + case VALUE_STRING: + if (INDEX.match(currentFieldName)) { + index = parser.text(); + } else if (TYPE.match(currentFieldName)) { + type = parser.text(); + } else if (ID.match(currentFieldName)) { + id = parser.text(); + } + break; + case START_OBJECT: + if (ERROR.match(currentFieldName)) { + exception = ElasticsearchException.fromXContent(parser); + } + break; + default: + // If unknown tokens are encounter then these should be ignored, because + // this is parsing logic on the client side. + break; + } + if (getResult != null) { + break; + } + } + + if (exception != null) { + return new MultiGetItemResponse(null, new Failure(index, type, id, exception)); + } else { + GetResponse getResponse = new GetResponse(getResult); + return new MultiGetItemResponse(getResponse, null); + } } @Override diff --git a/server/src/main/java/org/elasticsearch/index/VersionType.java b/server/src/main/java/org/elasticsearch/index/VersionType.java index c5094ea185..6a8214cb0b 100644 --- a/server/src/main/java/org/elasticsearch/index/VersionType.java +++ b/server/src/main/java/org/elasticsearch/index/VersionType.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.lucene.uid.Versions; import java.io.IOException; +import java.util.Locale; public enum VersionType implements Writeable { INTERNAL((byte) 0) { @@ -350,6 +351,10 @@ public enum VersionType implements Writeable { return fromString(versionType); } + public static String toString(VersionType versionType) { + return versionType.name().toLowerCase(Locale.ROOT); + } + public static VersionType fromValue(byte value) { if (value == 0) { return INTERNAL; diff --git a/server/src/main/java/org/elasticsearch/index/get/GetResult.java b/server/src/main/java/org/elasticsearch/index/get/GetResult.java index 75e283b419..4cdf2a4892 100644 --- a/server/src/main/java/org/elasticsearch/index/get/GetResult.java +++ b/server/src/main/java/org/elasticsearch/index/get/GetResult.java @@ -269,14 +269,19 @@ public class GetResult implements Streamable, Iterable<DocumentField>, ToXConten public static GetResult fromXContentEmbedded(XContentParser parser) throws IOException { XContentParser.Token token = parser.nextToken(); ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + return fromXContentEmbedded(parser, null, null, null); + } + + public static GetResult fromXContentEmbedded(XContentParser parser, String index, String type, String id) throws IOException { + XContentParser.Token token = parser.currentToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); String currentFieldName = parser.currentName(); - String index = null, type = null, id = null; long version = -1; Boolean found = null; BytesReference source = null; Map<String, DocumentField> fields = new HashMap<>(); - while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); } else if (token.isValue()) { diff --git a/server/src/test/java/org/elasticsearch/action/get/MultiGetRequestTests.java b/server/src/test/java/org/elasticsearch/action/get/MultiGetRequestTests.java index 73c77d0629..8834ee203f 100644 --- a/server/src/test/java/org/elasticsearch/action/get/MultiGetRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/get/MultiGetRequestTests.java @@ -20,15 +20,21 @@ package org.elasticsearch.action.get; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.VersionType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.test.ESTestCase; import java.io.IOException; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; public class MultiGetRequestTests extends ESTestCase { @@ -129,4 +135,59 @@ public class MultiGetRequestTests extends ESTestCase { assertEquals(2, multiGetRequest.getItems().size()); } + + public void testXContentSerialization() throws IOException { + for (int runs = 0; runs < 20; runs++) { + MultiGetRequest expected = createTestInstance(); + XContentType xContentType = randomFrom(XContentType.values()); + BytesReference shuffled = toShuffledXContent(expected, xContentType, ToXContent.EMPTY_PARAMS, false); + XContentParser parser = createParser(XContentFactory.xContent(xContentType), shuffled); + MultiGetRequest actual = new MultiGetRequest(); + actual.add(null, null, null, null, null, parser, true); + assertThat(parser.nextToken(), nullValue()); + + assertThat(actual.items.size(), equalTo(expected.items.size())); + for (int i = 0; i < expected.items.size(); i++) { + MultiGetRequest.Item expectedItem = expected.items.get(i); + MultiGetRequest.Item actualItem = actual.items.get(i); + assertThat(actualItem, equalTo(expectedItem)); + } + } + } + + private MultiGetRequest createTestInstance() { + int numItems = randomIntBetween(0, 128); + MultiGetRequest request = new MultiGetRequest(); + for (int i = 0; i < numItems; i++) { + MultiGetRequest.Item item = new MultiGetRequest.Item(randomAlphaOfLength(4), randomAlphaOfLength(4), randomAlphaOfLength(4)); + if (randomBoolean()) { + item.version(randomNonNegativeLong()); + } + if (randomBoolean()) { + item.versionType(randomFrom(VersionType.values())); + } + if (randomBoolean()) { + FetchSourceContext fetchSourceContext; + if (randomBoolean()) { + fetchSourceContext = new FetchSourceContext(true, generateRandomStringArray(16, 8, false), + generateRandomStringArray(5, 4, false)); + } else { + fetchSourceContext = new FetchSourceContext(false); + } + item.fetchSourceContext(fetchSourceContext); + } + if (randomBoolean()) { + item.storedFields(generateRandomStringArray(16, 8, false)); + } + if (randomBoolean()) { + item.routing(randomAlphaOfLength(4)); + } + if (randomBoolean()) { + item.parent(randomAlphaOfLength(4)); + } + request.add(item); + } + return request; + } + } |