Add fromXContent method to GetResponse (#22082)
Moved field values `toXContent` logic to `GetField` (from `GetResult`), which outputs its own fields, and can also parse them now. Also added `fromXContent` to `GetResult` and `GetResponse`. The start object and end object for `GetResponse` output have been moved to `GetResult#toXContent`, from the corresponding rest action. This makes it possible to have `toXContent` and `fromXContent` completely symmetric, as parsing requires looping till an end object is found which is weird when the corresponding `toXContent` doesn't print that out. This also introduces the foundation for testing retrieval of _source and stored field values.
This commit is contained in:
parent
5bec4f8024
commit
3421e54a42
|
@ -349,7 +349,6 @@
|
|||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]fielddata[/\\]plain[/\\]ParentChildIndexFieldData.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]fielddata[/\\]plain[/\\]SortedNumericDVIndexFieldData.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]fielddata[/\\]plain[/\\]SortedSetDVOrdinalsIndexFieldData.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]get[/\\]GetResult.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]get[/\\]ShardGetService.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]mapper[/\\]DocumentFieldMappers.java" checks="LineLength" />
|
||||
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]index[/\\]mapper[/\\]DocumentMapper.java" checks="LineLength" />
|
||||
|
|
|
@ -44,7 +44,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
||||
|
||||
/**
|
||||
|
@ -357,7 +357,8 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
|
|||
* instances.
|
||||
*/
|
||||
public static ElasticsearchException fromXContent(XContentParser parser) throws IOException {
|
||||
XContentParser.Token token = ensureFieldName(parser.nextToken(), parser::getTokenLocation);
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
|
||||
|
||||
String type = null, reason = null, stack = null;
|
||||
ElasticsearchException cause = null;
|
||||
|
|
|
@ -27,12 +27,14 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.get.GetField;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The response of a get action.
|
||||
|
@ -42,7 +44,7 @@ import java.util.Map;
|
|||
*/
|
||||
public class GetResponse extends ActionResponse implements Iterable<GetField>, ToXContent {
|
||||
|
||||
private GetResult getResult;
|
||||
GetResult getResult;
|
||||
|
||||
GetResponse() {
|
||||
}
|
||||
|
@ -156,6 +158,11 @@ public class GetResponse extends ActionResponse implements Iterable<GetField>, T
|
|||
return getResult.toXContent(builder, params);
|
||||
}
|
||||
|
||||
public static GetResponse fromXContent(XContentParser parser) throws IOException {
|
||||
GetResult getResult = GetResult.fromXContent(parser);
|
||||
return new GetResponse(getResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
|
@ -168,6 +175,23 @@ public class GetResponse extends ActionResponse implements Iterable<GetField>, T
|
|||
getResult.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GetResponse getResponse = (GetResponse) o;
|
||||
return Objects.equals(getResult, getResponse.getResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Strings.toString(this, true);
|
||||
|
|
|
@ -140,9 +140,7 @@ public class MultiGetResponse extends ActionResponse implements Iterable<MultiGe
|
|||
builder.endObject();
|
||||
} else {
|
||||
GetResponse getResponse = response.getResponse();
|
||||
builder.startObject();
|
||||
getResponse.toXContent(builder, params);
|
||||
builder.endObject();
|
||||
}
|
||||
}
|
||||
builder.endArray();
|
||||
|
@ -154,9 +152,6 @@ public class MultiGetResponse extends ActionResponse implements Iterable<MultiGe
|
|||
static final String _INDEX = "_index";
|
||||
static final String _TYPE = "_type";
|
||||
static final String _ID = "_id";
|
||||
static final String ERROR = "error";
|
||||
static final String ROOT_CAUSE = "root_cause";
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -100,7 +100,10 @@ public final class Text implements Comparable<Text> {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return bytes().equals(((Text) obj).bytes());
|
||||
|
|
|
@ -349,4 +349,22 @@ public class XContentHelper {
|
|||
builder.rawField(field, source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes that represent the XContent output of the provided {@link ToXContent} object, using the provided
|
||||
* {@link XContentType}. Wraps the output into a new anonymous object depending on the value of the wrapInObject argument.
|
||||
*/
|
||||
public static BytesReference toXContent(ToXContent toXContent, XContentType xContentType, boolean wrapInObject) throws IOException {
|
||||
try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
|
||||
if (wrapInObject) {
|
||||
builder.startObject();
|
||||
}
|
||||
toXContent.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
if (wrapInObject) {
|
||||
builder.endObject();
|
||||
}
|
||||
return builder.bytes();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.common.xcontent;
|
|||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser.Token;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -35,34 +34,6 @@ public final class XContentParserUtils {
|
|||
private XContentParserUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of type {@link XContentParser.Token#FIELD_NAME}
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not of type {@link XContentParser.Token#FIELD_NAME}
|
||||
*/
|
||||
public static Token ensureFieldName(Token token, Supplier<XContentLocation> location) throws IOException {
|
||||
return ensureType(Token.FIELD_NAME, token, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of type {@link XContentParser.Token#FIELD_NAME} and the the field name is equal to the provided one
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not of type {@link XContentParser.Token#FIELD_NAME} or is not equal to the given
|
||||
* field name
|
||||
*/
|
||||
public static Token ensureFieldName(XContentParser parser, Token token, String fieldName) throws IOException {
|
||||
Token t = ensureType(Token.FIELD_NAME, token, parser::getTokenLocation);
|
||||
|
||||
String current = parser.currentName() != null ? parser.currentName() : "<null>";
|
||||
if (current.equals(fieldName) == false) {
|
||||
String message = "Failed to parse object: expecting field with name [%s] but found [%s]";
|
||||
throw new ParsingException(parser.getTokenLocation(), String.format(Locale.ROOT, message, fieldName, current));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ParsingException with a "unknown field found" reason
|
||||
*/
|
||||
|
@ -72,16 +43,14 @@ public final class XContentParserUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Makes sure that current token is of the expected type
|
||||
* Makes sure that provided token is of the expected type
|
||||
*
|
||||
* @return the token
|
||||
* @throws ParsingException if the token is not equal to the expected type
|
||||
*/
|
||||
private static Token ensureType(Token expected, Token current, Supplier<XContentLocation> location) {
|
||||
if (current != expected) {
|
||||
public static void ensureExpectedToken(Token expected, Token actual, Supplier<XContentLocation> location) {
|
||||
if (actual != expected) {
|
||||
String message = "Failed to parse object: expecting token of type [%s] but found [%s]";
|
||||
throw new ParsingException(location.get(), String.format(Locale.ROOT, message, expected, current));
|
||||
throw new ParsingException(location.get(), String.format(Locale.ROOT, message, expected, actual));
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,17 +19,25 @@
|
|||
|
||||
package org.elasticsearch.index.get;
|
||||
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Streamable;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class GetField implements Streamable, Iterable<Object> {
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||
|
||||
public class GetField implements Streamable, ToXContent, Iterable<Object> {
|
||||
|
||||
private String name;
|
||||
private List<Object> values;
|
||||
|
@ -38,8 +46,8 @@ public class GetField implements Streamable, Iterable<Object> {
|
|||
}
|
||||
|
||||
public GetField(String name, List<Object> values) {
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.name = Objects.requireNonNull(name, "name must not be null");
|
||||
this.values = Objects.requireNonNull(values, "values must not be null");
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
@ -90,4 +98,67 @@ public class GetField implements Streamable, Iterable<Object> {
|
|||
out.writeGenericValue(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startArray(name);
|
||||
for (Object value : values) {
|
||||
//this call doesn't really need to support writing any kind of object.
|
||||
//Stored fields values are converted using MappedFieldType#valueForDisplay.
|
||||
//As a result they can either be Strings, Numbers, Booleans, or BytesReference, that's all.
|
||||
builder.value(value);
|
||||
}
|
||||
builder.endArray();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static GetField fromXContent(XContentParser parser) throws IOException {
|
||||
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation);
|
||||
String fieldName = parser.currentName();
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
ensureExpectedToken(XContentParser.Token.START_ARRAY, token, parser::getTokenLocation);
|
||||
List<Object> values = new ArrayList<>();
|
||||
while((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||
Object value;
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
value = parser.text();
|
||||
} else if (token == XContentParser.Token.VALUE_NUMBER) {
|
||||
value = parser.numberValue();
|
||||
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
|
||||
value = parser.booleanValue();
|
||||
} else if (token == XContentParser.Token.VALUE_EMBEDDED_OBJECT) {
|
||||
value = new BytesArray(parser.binaryValue());
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Failed to parse object: unsupported token found [" + token + "]");
|
||||
}
|
||||
values.add(value);
|
||||
}
|
||||
return new GetField(fieldName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GetField objects = (GetField) o;
|
||||
return Objects.equals(name, objects.name) &&
|
||||
Objects.equals(values, objects.values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetField{" +
|
||||
"name='" + name + '\'' +
|
||||
", values=" + values +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.common.io.stream.Streamable;
|
|||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.SourceFieldMapper;
|
||||
import org.elasticsearch.search.lookup.SourceLookup;
|
||||
|
||||
|
@ -38,12 +39,22 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField;
|
||||
import static org.elasticsearch.index.get.GetField.readGetField;
|
||||
|
||||
public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
||||
|
||||
private static final String _INDEX = "_index";
|
||||
private static final String _TYPE = "_type";
|
||||
private static final String _ID = "_id";
|
||||
private static final String _VERSION = "_version";
|
||||
private static final String FOUND = "found";
|
||||
private static final String FIELDS = "fields";
|
||||
|
||||
private String index;
|
||||
private String type;
|
||||
private String id;
|
||||
|
@ -57,7 +68,8 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
|||
GetResult() {
|
||||
}
|
||||
|
||||
public GetResult(String index, String type, String id, long version, boolean exists, BytesReference source, Map<String, GetField> fields) {
|
||||
public GetResult(String index, String type, String id, long version, boolean exists, BytesReference source,
|
||||
Map<String, GetField> fields) {
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
|
@ -196,15 +208,6 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
|||
return fields.values().iterator();
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final String _INDEX = "_index";
|
||||
static final String _TYPE = "_type";
|
||||
static final String _ID = "_id";
|
||||
static final String _VERSION = "_version";
|
||||
static final String FOUND = "found";
|
||||
static final String FIELDS = "fields";
|
||||
}
|
||||
|
||||
public XContentBuilder toXContentEmbedded(XContentBuilder builder, Params params) throws IOException {
|
||||
List<GetField> metaFields = new ArrayList<>();
|
||||
List<GetField> otherFields = new ArrayList<>();
|
||||
|
@ -225,20 +228,16 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
|||
builder.field(field.getName(), field.getValue());
|
||||
}
|
||||
|
||||
builder.field(Fields.FOUND, exists);
|
||||
builder.field(FOUND, exists);
|
||||
|
||||
if (source != null) {
|
||||
XContentHelper.writeRawField(SourceFieldMapper.NAME, source, builder, params);
|
||||
}
|
||||
|
||||
if (!otherFields.isEmpty()) {
|
||||
builder.startObject(Fields.FIELDS);
|
||||
builder.startObject(FIELDS);
|
||||
for (GetField field : otherFields) {
|
||||
builder.startArray(field.getName());
|
||||
for (Object value : field.getValues()) {
|
||||
builder.value(value);
|
||||
}
|
||||
builder.endArray();
|
||||
field.toXContent(builder, params);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -247,23 +246,69 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
|||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (!isExists()) {
|
||||
builder.field(Fields._INDEX, index);
|
||||
builder.field(Fields._TYPE, type);
|
||||
builder.field(Fields._ID, id);
|
||||
builder.field(Fields.FOUND, false);
|
||||
} else {
|
||||
builder.field(Fields._INDEX, index);
|
||||
builder.field(Fields._TYPE, type);
|
||||
builder.field(Fields._ID, id);
|
||||
builder.startObject();
|
||||
builder.field(_INDEX, index);
|
||||
builder.field(_TYPE, type);
|
||||
builder.field(_ID, id);
|
||||
if (isExists()) {
|
||||
if (version != -1) {
|
||||
builder.field(Fields._VERSION, version);
|
||||
builder.field(_VERSION, version);
|
||||
}
|
||||
toXContentEmbedded(builder, params);
|
||||
} else {
|
||||
builder.field(FOUND, false);
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static GetResult fromXContent(XContentParser parser) throws IOException {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
|
||||
String currentFieldName = null;
|
||||
String index = null, type = null, id = null;
|
||||
long version = -1;
|
||||
boolean found = false;
|
||||
BytesReference source = null;
|
||||
Map<String, GetField> fields = new HashMap<>();
|
||||
while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if (_INDEX.equals(currentFieldName)) {
|
||||
index = parser.text();
|
||||
} else if (_TYPE.equals(currentFieldName)) {
|
||||
type = parser.text();
|
||||
} else if (_ID.equals(currentFieldName)) {
|
||||
id = parser.text();
|
||||
} else if (_VERSION.equals(currentFieldName)) {
|
||||
version = parser.longValue();
|
||||
} else if (FOUND.equals(currentFieldName)) {
|
||||
found = parser.booleanValue();
|
||||
} else {
|
||||
fields.put(currentFieldName, new GetField(currentFieldName, Collections.singletonList(parser.objectText())));
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if (SourceFieldMapper.NAME.equals(currentFieldName)) {
|
||||
try (XContentBuilder builder = XContentBuilder.builder(parser.contentType().xContent())) {
|
||||
//the original document gets slightly modified: whitespaces or pretty printing are not preserved,
|
||||
//it all depends on the current builder settings
|
||||
builder.copyCurrentStructure(parser);
|
||||
source = builder.bytes();
|
||||
}
|
||||
} else if (FIELDS.equals(currentFieldName)) {
|
||||
while(parser.nextToken() != XContentParser.Token.END_OBJECT) {
|
||||
GetField getField = GetField.fromXContent(parser);
|
||||
fields.put(getField.getName(), getField);
|
||||
}
|
||||
} else {
|
||||
throwUnknownField(currentFieldName, parser.getTokenLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new GetResult(index, type, id, version, found, source, fields);
|
||||
}
|
||||
|
||||
public static GetResult readGetResult(StreamInput in) throws IOException {
|
||||
GetResult result = new GetResult();
|
||||
result.readFrom(in);
|
||||
|
@ -314,5 +359,28 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GetResult getResult = (GetResult) o;
|
||||
return version == getResult.version &&
|
||||
exists == getResult.exists &&
|
||||
Objects.equals(index, getResult.index) &&
|
||||
Objects.equals(type, getResult.type) &&
|
||||
Objects.equals(id, getResult.id) &&
|
||||
Objects.equals(fields, getResult.fields) &&
|
||||
Objects.equals(sourceAsMap(), getResult.sourceAsMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(index, type, id, version, exists, fields, sourceAsMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,12 +42,23 @@ public class RestToXContentListener<Response extends ToXContent> extends RestRes
|
|||
}
|
||||
|
||||
public final RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
|
||||
builder.startObject();
|
||||
if (wrapInObject()) {
|
||||
builder.startObject();
|
||||
}
|
||||
response.toXContent(builder, channel.request());
|
||||
builder.endObject();
|
||||
if (wrapInObject()) {
|
||||
builder.endObject();
|
||||
}
|
||||
return new BytesRestResponse(getStatus(response), builder);
|
||||
}
|
||||
|
||||
protected boolean wrapInObject() {
|
||||
//Ideally, the toXContent method starts with startObject and ends with endObject.
|
||||
//In practice, we have many places where toXContent produces a json fragment that's not valid by itself. We will
|
||||
//migrate those step by step, so that we never have to start objects here, and we can remove this method.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected RestStatus getStatus(Response response) {
|
||||
return RestStatus.OK;
|
||||
}
|
||||
|
|
|
@ -25,15 +25,13 @@ import org.elasticsearch.client.node.NodeClient;
|
|||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.VersionType;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestActions;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
import org.elasticsearch.rest.action.RestToXContentListener;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -76,17 +74,15 @@ public class RestGetAction extends BaseRestHandler {
|
|||
|
||||
getRequest.fetchSourceContext(FetchSourceContext.parseFromRestRequest(request));
|
||||
|
||||
return channel -> client.get(getRequest, new RestBuilderListener<GetResponse>(channel) {
|
||||
return channel -> client.get(getRequest, new RestToXContentListener<GetResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(GetResponse response, XContentBuilder builder) throws Exception {
|
||||
builder.startObject();
|
||||
response.toXContent(builder, request);
|
||||
builder.endObject();
|
||||
if (!response.isExists()) {
|
||||
return new BytesRestResponse(NOT_FOUND, builder);
|
||||
} else {
|
||||
return new BytesRestResponse(OK, builder);
|
||||
}
|
||||
protected boolean wrapInObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RestStatus getStatus(GetResponse response) {
|
||||
return response.isExists() ? OK : NOT_FOUND;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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.action.get;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.get.GetField;
|
||||
import org.elasticsearch.index.get.GetResult;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||
import static org.elasticsearch.index.get.GetResultTests.copyGetResult;
|
||||
import static org.elasticsearch.index.get.GetResultTests.mutateGetResult;
|
||||
import static org.elasticsearch.index.get.GetResultTests.randomGetResult;
|
||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
|
||||
|
||||
public class GetResponseTests extends ESTestCase {
|
||||
|
||||
public void testToAndFromXContent() throws Exception {
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
|
||||
GetResponse getResponse = new GetResponse(tuple.v1());
|
||||
GetResponse expectedGetResponse = new GetResponse(tuple.v2());
|
||||
BytesReference originalBytes = toXContent(getResponse, xContentType, false);
|
||||
//test that we can parse what we print out
|
||||
GetResponse parsedGetResponse;
|
||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||
parsedGetResponse = GetResponse.fromXContent(parser);
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
assertEquals(expectedGetResponse, parsedGetResponse);
|
||||
//print the parsed object out and test that the output is the same as the original output
|
||||
BytesReference finalBytes = toXContent(parsedGetResponse, xContentType, false);
|
||||
assertEquivalent(originalBytes, finalBytes, xContentType);
|
||||
//check that the source stays unchanged, no shuffling of keys nor anything like that
|
||||
assertEquals(expectedGetResponse.getSourceAsString(), parsedGetResponse.getSourceAsString());
|
||||
|
||||
}
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
{
|
||||
GetResponse getResponse = new GetResponse(new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " +
|
||||
"\"value1\", \"field2\":\"value2\"}"), Collections.singletonMap("field1", new GetField("field1",
|
||||
Collections.singletonList("value1")))));
|
||||
String output = Strings.toString(getResponse, false);
|
||||
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " +
|
||||
": \"value1\", \"field2\":\"value2\"},\"fields\":{\"field1\":[\"value1\"]}}", output);
|
||||
}
|
||||
{
|
||||
GetResponse getResponse = new GetResponse(new GetResult("index", "type", "id", 1, false, null, null));
|
||||
String output = Strings.toString(getResponse, false);
|
||||
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"found\":false}", output);
|
||||
}
|
||||
}
|
||||
|
||||
public void testEqualsAndHashcode() {
|
||||
checkEqualsAndHashCode(new GetResponse(randomGetResult(XContentType.JSON).v1()), GetResponseTests::copyGetResponse,
|
||||
GetResponseTests::mutateGetResponse);
|
||||
}
|
||||
private static GetResponse copyGetResponse(GetResponse getResponse) {
|
||||
return new GetResponse(copyGetResult(getResponse.getResult));
|
||||
}
|
||||
|
||||
private static GetResponse mutateGetResponse(GetResponse getResponse) {
|
||||
return new GetResponse(mutateGetResult(getResponse.getResult));
|
||||
}
|
||||
}
|
|
@ -20,62 +20,25 @@
|
|||
package org.elasticsearch.common.xcontent;
|
||||
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
||||
|
||||
public class XContentParserUtilsTests extends ESTestCase {
|
||||
|
||||
private XContentType xContentType;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
xContentType = randomFrom(XContentType.values());
|
||||
}
|
||||
|
||||
public void testEnsureFieldName() throws IOException {
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().endObject().bytes());
|
||||
public void testEnsureExpectedToken() throws IOException {
|
||||
final XContentParser.Token randomToken = randomFrom(XContentParser.Token.values());
|
||||
try (XContentParser parser = createParser(JsonXContent.jsonXContent, "{}")) {
|
||||
// Parser current token is null
|
||||
assertNull(parser.currentToken());
|
||||
XContentParserUtils.ensureFieldName(parser.currentToken(), parser::getTokenLocation);
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting token of type [FIELD_NAME] but found [null]"));
|
||||
|
||||
e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().field("foo", "bar").endObject().bytes());
|
||||
// Parser next token is a start object
|
||||
XContentParserUtils.ensureFieldName(parser.nextToken(), parser::getTokenLocation);
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting token of type [FIELD_NAME] but found [START_OBJECT]"));
|
||||
|
||||
e = expectThrows(ParsingException.class, () -> {
|
||||
XContentParser parser = createParser(createBuilder().startObject().field("foo", "bar").endObject().bytes());
|
||||
// Moves to start object
|
||||
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
|
||||
// Expected field name is "foo", not "test"
|
||||
XContentParserUtils.ensureFieldName(parser, parser.nextToken(), "test");
|
||||
});
|
||||
assertThat(e.getMessage(), equalTo("Failed to parse object: expecting field with name [test] but found [foo]"));
|
||||
|
||||
// Everything is fine
|
||||
final String randomFieldName = randomAsciiOfLength(5);
|
||||
XContentParser parser = createParser(createBuilder().startObject().field(randomFieldName, 0).endObject().bytes());
|
||||
assertThat(parser.nextToken(), is(XContentParser.Token.START_OBJECT));
|
||||
XContentParserUtils.ensureFieldName(parser, parser.nextToken(), randomFieldName);
|
||||
}
|
||||
|
||||
private XContentBuilder createBuilder() throws IOException {
|
||||
return XContentBuilder.builder(xContentType.xContent());
|
||||
}
|
||||
|
||||
private XContentParser createParser(BytesReference bytes) throws IOException {
|
||||
return xContentType.xContent().createParser(bytes);
|
||||
ParsingException e = expectThrows(ParsingException.class,
|
||||
() -> ensureExpectedToken(randomToken, parser.currentToken(), parser::getTokenLocation));
|
||||
assertEquals("Failed to parse object: expecting token of type [" + randomToken + "] but found [null]", e.getMessage());
|
||||
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
||||
ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.index.get;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.mapper.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.RoutingFieldMapper;
|
||||
import org.elasticsearch.index.mapper.UidFieldMapper;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.RandomObjects;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
|
||||
|
||||
public class GetFieldTests extends ESTestCase {
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
GetField getField = new GetField("field", Arrays.asList("value1", "value2"));
|
||||
String output = Strings.toString(getField, true);
|
||||
assertEquals("{\"field\":[\"value1\",\"value2\"]}", output);
|
||||
}
|
||||
|
||||
public void testEqualsAndHashcode() {
|
||||
checkEqualsAndHashCode(randomGetField(XContentType.JSON).v1(), GetFieldTests::copyGetField, GetFieldTests::mutateGetField);
|
||||
}
|
||||
|
||||
public void testToAndFromXContent() throws Exception {
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
Tuple<GetField, GetField> tuple = randomGetField(xContentType);
|
||||
GetField getField = tuple.v1();
|
||||
GetField expectedGetField = tuple.v2();
|
||||
BytesReference originalBytes = toXContent(getField, xContentType, true);
|
||||
//test that we can parse what we print out
|
||||
GetField parsedGetField;
|
||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||
//we need to move to the next token, the start object one that we manually added is not expected
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
|
||||
parsedGetField = GetField.fromXContent(parser);
|
||||
assertEquals(XContentParser.Token.END_ARRAY, parser.currentToken());
|
||||
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
assertEquals(expectedGetField, parsedGetField);
|
||||
BytesReference finalBytes = toXContent(parsedGetField, xContentType, true);
|
||||
assertEquivalent(originalBytes, finalBytes, xContentType);
|
||||
}
|
||||
|
||||
private static GetField copyGetField(GetField getField) {
|
||||
return new GetField(getField.getName(), getField.getValues());
|
||||
}
|
||||
|
||||
private static GetField mutateGetField(GetField getField) {
|
||||
List<Supplier<GetField>> mutations = new ArrayList<>();
|
||||
mutations.add(() -> new GetField(randomUnicodeOfCodepointLength(15), getField.getValues()));
|
||||
mutations.add(() -> new GetField(getField.getName(), randomGetField(XContentType.JSON).v1().getValues()));
|
||||
return randomFrom(mutations).get();
|
||||
}
|
||||
|
||||
public static Tuple<GetField, GetField> randomGetField(XContentType xContentType) {
|
||||
if (randomBoolean()) {
|
||||
String fieldName = randomFrom(ParentFieldMapper.NAME, RoutingFieldMapper.NAME, UidFieldMapper.NAME);
|
||||
GetField getField = new GetField(fieldName, Collections.singletonList(randomAsciiOfLengthBetween(3, 10)));
|
||||
return Tuple.tuple(getField, getField);
|
||||
}
|
||||
String fieldName = randomAsciiOfLengthBetween(3, 10);
|
||||
Tuple<List<Object>, List<Object>> tuple = RandomObjects.randomStoredFieldValues(random(), xContentType);
|
||||
GetField input = new GetField(fieldName, tuple.v1());
|
||||
GetField expected = new GetField(fieldName, tuple.v2());
|
||||
return Tuple.tuple(input, expected);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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.index.get;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.RandomObjects;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
|
||||
import static org.elasticsearch.index.get.GetFieldTests.randomGetField;
|
||||
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertEquivalent;
|
||||
|
||||
public class GetResultTests extends ESTestCase {
|
||||
|
||||
public void testToAndFromXContent() throws Exception {
|
||||
XContentType xContentType = randomFrom(XContentType.values());
|
||||
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
|
||||
GetResult getResult = tuple.v1();
|
||||
GetResult expectedGetResult = tuple.v2();
|
||||
BytesReference originalBytes = toXContent(getResult, xContentType, false);
|
||||
//test that we can parse what we print out
|
||||
GetResult parsedGetResult;
|
||||
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
|
||||
parsedGetResult = GetResult.fromXContent(parser);
|
||||
assertNull(parser.nextToken());
|
||||
}
|
||||
assertEquals(expectedGetResult, parsedGetResult);
|
||||
//print the parsed object out and test that the output is the same as the original output
|
||||
BytesReference finalBytes = toXContent(parsedGetResult, xContentType, false);
|
||||
assertEquivalent(originalBytes, finalBytes, xContentType);
|
||||
//check that the source stays unchanged, no shuffling of keys nor anything like that
|
||||
assertEquals(expectedGetResult.sourceAsString(), parsedGetResult.sourceAsString());
|
||||
}
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
{
|
||||
GetResult getResult = new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " +
|
||||
"\"value1\", \"field2\":\"value2\"}"), Collections.singletonMap("field1", new GetField("field1",
|
||||
Collections.singletonList("value1"))));
|
||||
String output = Strings.toString(getResult, false);
|
||||
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " +
|
||||
": \"value1\", \"field2\":\"value2\"},\"fields\":{\"field1\":[\"value1\"]}}", output);
|
||||
}
|
||||
{
|
||||
GetResult getResult = new GetResult("index", "type", "id", 1, false, null, null);
|
||||
String output = Strings.toString(getResult, false);
|
||||
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"found\":false}", output);
|
||||
}
|
||||
}
|
||||
|
||||
public void testEqualsAndHashcode() {
|
||||
checkEqualsAndHashCode(randomGetResult(XContentType.JSON).v1(), GetResultTests::copyGetResult, GetResultTests::mutateGetResult);
|
||||
}
|
||||
|
||||
public static GetResult copyGetResult(GetResult getResult) {
|
||||
return new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), getResult.getFields());
|
||||
}
|
||||
|
||||
public static GetResult mutateGetResult(GetResult getResult) {
|
||||
List<Supplier<GetResult>> mutations = new ArrayList<>();
|
||||
mutations.add(() -> new GetResult(randomUnicodeOfLength(15), getResult.getType(), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), randomUnicodeOfLength(15), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), randomUnicodeOfLength(15), getResult.getVersion(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), randomPositiveLong(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists() == false, getResult.internalSourceRef(), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists(), RandomObjects.randomSource(random()), getResult.getFields()));
|
||||
mutations.add(() -> new GetResult(getResult.getIndex(), getResult.getType(), getResult.getId(), getResult.getVersion(),
|
||||
getResult.isExists(), getResult.internalSourceRef(), randomGetFields(XContentType.JSON).v1()));
|
||||
return randomFrom(mutations).get();
|
||||
}
|
||||
|
||||
public static Tuple<GetResult, GetResult> randomGetResult(XContentType xContentType) {
|
||||
final String index = randomAsciiOfLengthBetween(3, 10);
|
||||
final String type = randomAsciiOfLengthBetween(3, 10);
|
||||
final String id = randomAsciiOfLengthBetween(3, 10);
|
||||
final long version;
|
||||
final boolean exists;
|
||||
BytesReference source = null;
|
||||
Map<String, GetField> fields = null;
|
||||
Map<String, GetField> expectedFields = null;
|
||||
if (frequently()) {
|
||||
version = randomPositiveLong();
|
||||
exists = true;
|
||||
if (frequently()) {
|
||||
source = RandomObjects.randomSource(random());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
Tuple<Map<String, GetField>, Map<String, GetField>> tuple = randomGetFields(xContentType);
|
||||
fields = tuple.v1();
|
||||
expectedFields = tuple.v2();
|
||||
}
|
||||
} else {
|
||||
version = -1;
|
||||
exists = false;
|
||||
}
|
||||
GetResult getResult = new GetResult(index, type, id, version, exists, source, fields);
|
||||
GetResult expectedGetResult = new GetResult(index, type, id, version, exists, source, expectedFields);
|
||||
return Tuple.tuple(getResult, expectedGetResult);
|
||||
}
|
||||
|
||||
private static Tuple<Map<String, GetField>,Map<String, GetField>> randomGetFields(XContentType xContentType) {
|
||||
int numFields = randomIntBetween(2, 10);
|
||||
Map<String, GetField> fields = new HashMap<>(numFields);
|
||||
Map<String, GetField> expectedFields = new HashMap<>(numFields);
|
||||
for (int i = 0; i < numFields; i++) {
|
||||
Tuple<GetField, GetField> tuple = randomGetField(xContentType);
|
||||
GetField getField = tuple.v1();
|
||||
GetField expectedGetField = tuple.v2();
|
||||
fields.put(getField.getName(), getField);
|
||||
expectedFields.put(expectedGetField.getName(), expectedGetField);
|
||||
}
|
||||
return Tuple.tuple(fields, expectedFields);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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.test;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
||||
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.carrotsearch.randomizedtesting.generators.RandomStrings.randomUnicodeOfLengthBetween;
|
||||
|
||||
public final class RandomObjects {
|
||||
|
||||
private RandomObjects() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tuple containing random stored field values and their corresponding expected values once printed out
|
||||
* via {@link org.elasticsearch.common.xcontent.ToXContent#toXContent(XContentBuilder, ToXContent.Params)} and parsed back via
|
||||
* {@link org.elasticsearch.common.xcontent.XContentParser#objectText()}.
|
||||
* Generates values based on what can get printed out. Stored fields values are retrieved from lucene and converted via
|
||||
* {@link org.elasticsearch.index.mapper.MappedFieldType#valueForDisplay(Object)} to either strings, numbers or booleans.
|
||||
*
|
||||
* @param random Random generator
|
||||
* @param xContentType the content type, used to determine what the expected values are for float numbers.
|
||||
*/
|
||||
public static Tuple<List<Object>, List<Object>> randomStoredFieldValues(Random random, XContentType xContentType) {
|
||||
int numValues = RandomNumbers.randomIntBetween(random, 1, 5);
|
||||
List<Object> originalValues = new ArrayList<>();
|
||||
List<Object> expectedParsedValues = new ArrayList<>();
|
||||
int dataType = RandomNumbers.randomIntBetween(random, 0, 8);
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
switch(dataType) {
|
||||
case 0:
|
||||
long randomLong = random.nextLong();
|
||||
originalValues.add(randomLong);
|
||||
expectedParsedValues.add(randomLong);
|
||||
break;
|
||||
case 1:
|
||||
int randomInt = random.nextInt();
|
||||
originalValues.add(randomInt);
|
||||
expectedParsedValues.add(randomInt);
|
||||
break;
|
||||
case 2:
|
||||
Short randomShort = (short) random.nextInt();
|
||||
originalValues.add(randomShort);
|
||||
expectedParsedValues.add(randomShort.intValue());
|
||||
break;
|
||||
case 3:
|
||||
Byte randomByte = (byte)random.nextInt();
|
||||
originalValues.add(randomByte);
|
||||
expectedParsedValues.add(randomByte.intValue());
|
||||
break;
|
||||
case 4:
|
||||
double randomDouble = random.nextDouble();
|
||||
originalValues.add(randomDouble);
|
||||
expectedParsedValues.add(randomDouble);
|
||||
break;
|
||||
case 5:
|
||||
Float randomFloat = random.nextFloat();
|
||||
originalValues.add(randomFloat);
|
||||
if (xContentType == XContentType.CBOR) {
|
||||
//with CBOR we get back a float
|
||||
expectedParsedValues.add(randomFloat);
|
||||
} else if (xContentType == XContentType.SMILE) {
|
||||
//with SMILE we get back a double
|
||||
expectedParsedValues.add(randomFloat.doubleValue());
|
||||
} else {
|
||||
//with JSON AND YAML we get back a double, but with float precision.
|
||||
expectedParsedValues.add(Double.parseDouble(randomFloat.toString()));
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
boolean randomBoolean = random.nextBoolean();
|
||||
originalValues.add(randomBoolean);
|
||||
expectedParsedValues.add(randomBoolean);
|
||||
break;
|
||||
case 7:
|
||||
String randomString = random.nextBoolean() ? RandomStrings.randomAsciiOfLengthBetween(random, 3, 10 ) :
|
||||
randomUnicodeOfLengthBetween(random, 3, 10);
|
||||
originalValues.add(randomString);
|
||||
expectedParsedValues.add(randomString);
|
||||
break;
|
||||
case 8:
|
||||
byte[] randomBytes = RandomStrings.randomUnicodeOfLengthBetween(random, 10, 50).getBytes(StandardCharsets.UTF_8);
|
||||
BytesArray randomBytesArray = new BytesArray(randomBytes);
|
||||
originalValues.add(randomBytesArray);
|
||||
if (xContentType == XContentType.JSON || xContentType == XContentType.YAML) {
|
||||
//JSON and YAML write the base64 format
|
||||
expectedParsedValues.add(Base64.getEncoder().encodeToString(randomBytes));
|
||||
} else {
|
||||
//SMILE and CBOR write the original bytes as they support binary format
|
||||
expectedParsedValues.add(randomBytesArray);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
return Tuple.tuple(originalValues, expectedParsedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random source containing a random number of fields, objects and array, with maximum depth 5.
|
||||
*
|
||||
* @param random Random generator
|
||||
*/
|
||||
public static BytesReference randomSource(Random random) {
|
||||
//the source can be stored in any format and eventually converted when retrieved depending on the format of the response
|
||||
XContentType xContentType = RandomPicks.randomFrom(random, XContentType.values());
|
||||
try (XContentBuilder builder = XContentFactory.contentBuilder(xContentType)) {
|
||||
builder.startObject();
|
||||
addFields(random, builder, 0);
|
||||
builder.endObject();
|
||||
return builder.bytes();
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly adds fields, objects, or arrays to the provided builder. The maximum depth is 5.
|
||||
*/
|
||||
private static void addFields(Random random, XContentBuilder builder, int currentDepth) throws IOException {
|
||||
int numFields = RandomNumbers.randomIntBetween(random, 1, 5);
|
||||
for (int i = 0; i < numFields; i++) {
|
||||
if (currentDepth < 5 && random.nextBoolean()) {
|
||||
if (random.nextBoolean()) {
|
||||
builder.startObject(RandomStrings.randomAsciiOfLengthBetween(random, 3, 10));
|
||||
addFields(random, builder, currentDepth + 1);
|
||||
builder.endObject();
|
||||
} else {
|
||||
builder.startArray(RandomStrings.randomAsciiOfLengthBetween(random, 3, 10));
|
||||
int numElements = RandomNumbers.randomIntBetween(random, 1, 5);
|
||||
boolean object = random.nextBoolean();
|
||||
int dataType = -1;
|
||||
if (object == false) {
|
||||
dataType = randomDataType(random);
|
||||
}
|
||||
for (int j = 0; j < numElements; j++) {
|
||||
if (object) {
|
||||
builder.startObject();
|
||||
addFields(random, builder, 5);
|
||||
builder.endObject();
|
||||
} else {
|
||||
builder.value(randomFieldValue(random, dataType));
|
||||
}
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
} else {
|
||||
builder.field(RandomStrings.randomAsciiOfLengthBetween(random, 3, 10),
|
||||
randomFieldValue(random, randomDataType(random)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int randomDataType(Random random) {
|
||||
return RandomNumbers.randomIntBetween(random, 0, 3);
|
||||
}
|
||||
|
||||
private static Object randomFieldValue(Random random, int dataType) {
|
||||
switch(dataType) {
|
||||
case 0:
|
||||
return RandomStrings.randomAsciiOfLengthBetween(random, 3, 10);
|
||||
case 1:
|
||||
return RandomStrings.randomAsciiOfLengthBetween(random, 3, 10);
|
||||
case 2:
|
||||
return random.nextLong();
|
||||
case 3:
|
||||
return random.nextDouble();
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ import org.elasticsearch.cluster.block.ClusterBlock;
|
|||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
||||
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
|
@ -55,6 +56,8 @@ 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.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchModule;
|
||||
|
@ -75,6 +78,7 @@ import java.util.Arrays;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
@ -766,4 +770,62 @@ public class ElasticsearchAssertions {
|
|||
assertFileExists(dir);
|
||||
assertThat("file [" + dir + "] should be a directory.", Files.isDirectory(dir), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the provided {@link BytesReference}s hold the same content. The comparison is done between the map
|
||||
* representation of the provided objects.
|
||||
*/
|
||||
public static void assertEquivalent(BytesReference expected, BytesReference actual, XContentType xContentType) throws IOException {
|
||||
//we tried comparing byte per byte, but that didn't fly for a couple of reasons:
|
||||
//1) whenever anything goes through a map while parsing, ordering is not preserved, which is perfectly ok
|
||||
//2) Jackson SMILE parser parses floats as double, which then get printed out as double (with double precision)
|
||||
try (XContentParser actualParser = xContentType.xContent().createParser(actual)) {
|
||||
Map<String, Object> actualMap = actualParser.map();
|
||||
replaceBytesArrays(actualMap);
|
||||
try (XContentParser expectedParser = xContentType.xContent().createParser(expected)) {
|
||||
Map<String, Object> expectedMap = expectedParser.map();
|
||||
replaceBytesArrays(expectedMap);
|
||||
assertEquals(expectedMap, actualMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively navigates through the provided map argument and replaces every byte[] with a corresponding BytesArray object holding
|
||||
* the original byte[]. This helps maps to maps comparisons as arrays need to be compared using Arrays.equals otherwise their
|
||||
* references are compared, which is what happens in {@link java.util.AbstractMap#equals(Object)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void replaceBytesArrays(Map<String, Object> map) {
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
map.put(entry.getKey(), new BytesArray((byte[]) value));
|
||||
} else if (value instanceof Map) {
|
||||
replaceBytesArrays((Map<String, Object>) value);
|
||||
} else if (value instanceof List) {
|
||||
List<Object> list = (List<Object>) value;
|
||||
replaceBytesArrays(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively navigates through the provided list argument and replaces every byte[] with a corresponding BytesArray object holding
|
||||
* the original byte[]. This helps maps to maps comparisons as arrays need to be compared using Arrays.equals otherwise their
|
||||
* references are compared, which is what happens in {@link java.util.AbstractMap#equals(Object)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void replaceBytesArrays(List<Object> list) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Object object = list.get(i);
|
||||
if (object instanceof byte[]) {
|
||||
list.set(i, new BytesArray((byte[]) object));
|
||||
} else if (object instanceof Map) {
|
||||
replaceBytesArrays((Map<String, Object>) object);
|
||||
} else if (object instanceof List) {
|
||||
replaceBytesArrays((List<Object>) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue