Make reindex-from-remote ignore unknown fields

reindex-from-remote should ignore unknown fields so it is mostly
future compatible. This makes it ignore unknown fields by adding an
option to `ObjectParser` and `ConstructingObjectParser` that, if
enabled, causes them to ignore unknown fields.

Closes #20504
This commit is contained in:
Nik Everett 2016-09-20 17:01:00 -04:00
parent ee76c1a5c9
commit 370afa371b
8 changed files with 290 additions and 74 deletions

View File

@ -60,6 +60,9 @@ public abstract class AbstractObjectParser<Value, Context extends ParseFieldMatc
ValueType type);
public <T> void declareField(BiConsumer<Value, T> consumer, NoContextParser<T> parser, ParseField parseField, ValueType type) {
if (parser == null) {
throw new IllegalArgumentException("[parser] is required");
}
declareField(consumer, (p, c) -> parser.parse(p), parseField, type);
}

View File

@ -103,7 +103,7 @@ public final class ConstructingObjectParser<Value, Context extends ParseFieldMat
/**
* Build the parser.
*
*
* @param name The name given to the delegate ObjectParser for error identification. Use what you'd use if the object worked with
* ObjectParser.
* @param builder A function that builds the object from an array of Objects. Declare this inline with the parser, casting the elements
@ -113,7 +113,24 @@ public final class ConstructingObjectParser<Value, Context extends ParseFieldMat
* allocations.
*/
public ConstructingObjectParser(String name, Function<Object[], Value> builder) {
objectParser = new ObjectParser<>(name);
this(name, false, builder);
}
/**
* Build the parser.
*
* @param name The name given to the delegate ObjectParser for error identification. Use what you'd use if the object worked with
* ObjectParser.
* @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing responses
* from external systems, never when parsing requests from users.
* @param builder A function that builds the object from an array of Objects. Declare this inline with the parser, casting the elements
* of the array to the arguments so they work with your favorite constructor. The objects in the array will be in the same order
* that you declared the {{@link #constructorArg()}s and none will be null. If any of the constructor arguments aren't defined in
* the XContent then parsing will throw an error. We use an array here rather than a {@code Map<String, Object>} to save on
* allocations.
*/
public ConstructingObjectParser(String name, boolean ignoreUnknownFields, Function<Object[], Value> builder) {
objectParser = new ObjectParser<>(name, ignoreUnknownFields, null);
this.builder = builder;
}
@ -153,6 +170,19 @@ public final class ConstructingObjectParser<Value, Context extends ParseFieldMat
@Override
public <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Context, T> parser, ParseField parseField, ValueType type) {
if (consumer == null) {
throw new IllegalArgumentException("[consumer] is required");
}
if (parser == null) {
throw new IllegalArgumentException("[parser] is required");
}
if (parseField == null) {
throw new IllegalArgumentException("[parseField] is required");
}
if (type == null) {
throw new IllegalArgumentException("[type] is required");
}
if (consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER || consumer == OPTIONAL_CONSTRUCTOR_ARG_MARKER) {
/*
* Constructor arguments are detected by this "marker" consumer. It keeps the API looking clean even if it is a bit sleezy. We

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
@ -83,6 +84,11 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
private final Map<String, FieldParser> fieldParserMap = new HashMap<>();
private final String name;
private final Supplier<Value> valueSupplier;
/**
* Should this parser ignore unknown fields? This should generally be set to true only when parsing responses from external systems,
* never when parsing requests from users.
*/
private final boolean ignoreUnknownFields;
/**
* Creates a new ObjectParser instance with a name. This name is used to reference the parser in exceptions and messages.
@ -96,9 +102,21 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
* @param name the parsers name, used to reference the parser in exceptions and messages.
* @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
*/
public ObjectParser(String name, Supplier<Value> valueSupplier) {
public ObjectParser(String name, @Nullable Supplier<Value> valueSupplier) {
this(name, false, valueSupplier);
}
/**
* Creates a new ObjectParser instance which a name.
* @param name the parsers name, used to reference the parser in exceptions and messages.
* @param ignoreUnknownFields Should this parser ignore unknown fields? This should generally be set to true only when parsing
* responses from external systems, never when parsing requests from users.
* @param valueSupplier a supplier that creates a new Value instance used when the parser is used as an inner object parser.
*/
public ObjectParser(String name, boolean ignoreUnknownFields, @Nullable Supplier<Value> valueSupplier) {
this.name = name;
this.valueSupplier = valueSupplier;
this.ignoreUnknownFields = ignoreUnknownFields;
}
/**
@ -144,9 +162,13 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
if (currentFieldName == null) {
throw new IllegalStateException("[" + name + "] no field found");
}
assert fieldParser != null;
fieldParser.assertSupports(name, token, currentFieldName, context.getParseFieldMatcher());
parseSub(parser, fieldParser, currentFieldName, value, context);
if (fieldParser == null) {
assert ignoreUnknownFields : "this should only be possible if configured to ignore known fields";
parser.skipChildren(); // noop if parser points to a value, skips children if parser is start object or start array
} else {
fieldParser.assertSupports(name, token, currentFieldName, context.getParseFieldMatcher());
parseSub(parser, fieldParser, currentFieldName, value, context);
}
fieldParser = null;
}
}
@ -169,6 +191,12 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
void parse(XContentParser parser, Value value, Context context) throws IOException;
}
public void declareField(Parser<Value, Context> p, ParseField parseField, ValueType type) {
if (parseField == null) {
throw new IllegalArgumentException("[parseField] is required");
}
if (type == null) {
throw new IllegalArgumentException("[type] is required");
}
FieldParser fieldParser = new FieldParser(p, type.supportedTokens(), parseField, type);
for (String fieldValue : parseField.getAllNamesIncludedDeprecated()) {
fieldParserMap.putIfAbsent(fieldValue, fieldParser);
@ -178,6 +206,12 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
@Override
public <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Context, T> parser, ParseField parseField,
ValueType type) {
if (consumer == null) {
throw new IllegalArgumentException("[consumer] is required");
}
if (parser == null) {
throw new IllegalArgumentException("[parser] is required");
}
declareField((p, v, c) -> consumer.accept(v, parser.parse(p, c)), parseField, type);
}
@ -362,7 +396,7 @@ public final class ObjectParser<Value, Context extends ParseFieldMatcherSupplier
private FieldParser getParser(String fieldName) {
FieldParser<Value> parser = fieldParserMap.get(fieldName);
if (parser == null) {
if (parser == null && false == ignoreUnknownFields) {
throw new IllegalArgumentException("[" + name + "] unknown field [" + fieldName + "], parser not found");
}
return parser;

View File

@ -25,6 +25,8 @@ import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.AbstractObjectParser.ContextParser;
import org.elasticsearch.common.xcontent.AbstractObjectParser.NoContextParser;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matcher;
@ -43,6 +45,27 @@ import static org.hamcrest.Matchers.nullValue;
public class ConstructingObjectParserTests extends ESTestCase {
private static final ParseFieldMatcherSupplier MATCHER = () -> ParseFieldMatcher.STRICT;
public void testNullDeclares() {
ConstructingObjectParser<Void, ParseFieldMatcherSupplier> objectParser = new ConstructingObjectParser<>("foo", a -> null);
Exception e = expectThrows(IllegalArgumentException.class,
() -> objectParser.declareField(null, (r, c) -> null, new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[consumer] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (ContextParser<ParseFieldMatcherSupplier, Object>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (NoContextParser<Object>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, null, ObjectParser.ValueType.STRING));
assertEquals("[parseField] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, new ParseField("test"), null));
assertEquals("[type] is required", e.getMessage());
}
/**
* Builds the object in random order and parses it.
*/
@ -261,6 +284,25 @@ public class ConstructingObjectParserTests extends ESTestCase {
assertTrue(result.fooSet);
}
public void testIgnoreUnknownFields() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser(
"{\n"
+ " \"test\" : \"foo\",\n"
+ " \"junk\" : 2\n"
+ "}");
class TestStruct {
public final String test;
public TestStruct(String test) {
this.test = test;
}
}
ConstructingObjectParser<TestStruct, ParseFieldMatcherSupplier> objectParser = new ConstructingObjectParser<>("foo", true, a ->
new TestStruct((String) a[0]));
objectParser.declareString(constructorArg(), new ParseField("test"));
TestStruct s = objectParser.apply(parser, MATCHER);
assertEquals(s.test, "foo");
}
private static class HasCtorArguments implements ToXContent {
@Nullable
final String animal;

View File

@ -18,20 +18,22 @@
*/
package org.elasticsearch.common.xcontent;
import static org.hamcrest.Matchers.hasSize;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.AbstractObjectParser.ContextParser;
import org.elasticsearch.common.xcontent.AbstractObjectParser.NoContextParser;
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.Matchers.hasSize;
public class ObjectParserTests extends ESTestCase {
@ -72,6 +74,27 @@ public class ObjectParserTests extends ESTestCase {
+ "FieldParser{preferred_name=test_number, supportedTokens=[VALUE_STRING, VALUE_NUMBER], type=INT}]}");
}
public void testNullDeclares() {
ObjectParser<Void, ParseFieldMatcherSupplier> objectParser = new ObjectParser<>("foo");
Exception e = expectThrows(IllegalArgumentException.class,
() -> objectParser.declareField(null, (r, c) -> null, new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[consumer] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (ContextParser<ParseFieldMatcherSupplier, Object>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (NoContextParser<Object>) null,
new ParseField("test"), ObjectParser.ValueType.STRING));
assertEquals("[parser] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, null, ObjectParser.ValueType.STRING));
assertEquals("[parseField] is required", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> objectParser.declareField(
(o, v) -> {}, (r, c) -> null, new ParseField("test"), null));
assertEquals("[type] is required", e.getMessage());
}
public void testObjectOrDefault() throws IOException {
XContentParser parser = XContentType.JSON.xContent().createParser("{\"object\" : { \"test\": 2}}");
ObjectParser<StaticTestStruct, ParseFieldMatcherSupplier> objectParser = new ObjectParser<>("foo", StaticTestStruct::new);
@ -440,6 +463,77 @@ public class ObjectParserTests extends ESTestCase {
assertEquals("[named] doesn't support arrays. Use a single object with multiple fields.", e.getCause().getMessage());
}
public void testIgnoreUnknownFields() throws IOException {
XContentBuilder b = XContentBuilder.builder(XContentType.JSON.xContent());
b.startObject();
{
b.field("test", "foo");
b.field("junk", 2);
}
b.endObject();
b = shuffleXContent(b);
XContentParser parser = XContentType.JSON.xContent().createParser(b.bytes());
class TestStruct {
public String test;
}
ObjectParser<TestStruct, ParseFieldMatcherSupplier> objectParser = new ObjectParser<>("foo", true, null);
objectParser.declareField((i, c, x) -> c.test = i.text(), new ParseField("test"), ObjectParser.ValueType.STRING);
TestStruct s = objectParser.parse(parser, new TestStruct(), STRICT_PARSING);
assertEquals(s.test, "foo");
}
public void testIgnoreUnknownObjects() throws IOException {
XContentBuilder b = XContentBuilder.builder(XContentType.JSON.xContent());
b.startObject();
{
b.field("test", "foo");
b.startObject("junk");
{
b.field("really", "junk");
}
b.endObject();
}
b.endObject();
b = shuffleXContent(b);
XContentParser parser = XContentType.JSON.xContent().createParser(b.bytes());
class TestStruct {
public String test;
}
ObjectParser<TestStruct, ParseFieldMatcherSupplier> objectParser = new ObjectParser<>("foo", true, null);
objectParser.declareField((i, c, x) -> c.test = i.text(), new ParseField("test"), ObjectParser.ValueType.STRING);
TestStruct s = objectParser.parse(parser, new TestStruct(), STRICT_PARSING);
assertEquals(s.test, "foo");
}
public void testIgnoreUnknownArrays() throws IOException {
XContentBuilder b = XContentBuilder.builder(XContentType.JSON.xContent());
b.startObject();
{
b.field("test", "foo");
b.startArray("junk");
{
b.startObject();
{
b.field("really", "junk");
}
b.endObject();
}
b.endArray();
}
b.endObject();
b = shuffleXContent(b);
XContentParser parser = XContentType.JSON.xContent().createParser(b.bytes());
class TestStruct {
public String test;
}
ObjectParser<TestStruct, ParseFieldMatcherSupplier> objectParser = new ObjectParser<>("foo", true, null);
objectParser.declareField((i, c, x) -> c.test = i.text(), new ParseField("test"), ObjectParser.ValueType.STRING);
TestStruct s = objectParser.parse(parser, new TestStruct(), STRICT_PARSING);
assertEquals(s.test, "foo");
}
static class NamedObjectHolder {
public static final ObjectParser<NamedObjectHolder, ParseFieldMatcherSupplier> PARSER = new ObjectParser<>("named_object_holder",
NamedObjectHolder::new);

View File

@ -47,10 +47,7 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Parsers to convert the response from the remote host into objects useful for {@link RemoteScrollableHitSource}. Lots of data is
* intentionally thrown on the floor because we don't need it but ObjectParser and friends are strict about blowing up when they see
* elements they don't understand. So you'll see a lot of BiConsumers that look like "(b, v) -&gt; {}". That means "I don't care about the
* value here, just throw it away and don't blow up.
* Parsers to convert the response from the remote host into objects useful for {@link RemoteScrollableHitSource}.
*/
final class RemoteResponseParsers {
private RemoteResponseParsers() {}
@ -58,8 +55,8 @@ final class RemoteResponseParsers {
/**
* Parser for an individual {@code hit} element.
*/
public static final ConstructingObjectParser<BasicHit, ParseFieldMatcherSupplier> HIT_PARSER = new ConstructingObjectParser<>("hit",
a -> {
public static final ConstructingObjectParser<BasicHit, ParseFieldMatcherSupplier> HIT_PARSER =
new ConstructingObjectParser<>("hit", true, a -> {
int i = 0;
String index = (String) a[i++];
String type = (String) a[i++];
@ -90,26 +87,23 @@ final class RemoteResponseParsers {
HIT_PARSER.declareString(BasicHit::setParent, new ParseField("_parent"));
HIT_PARSER.declareLong(BasicHit::setTTL, new ParseField("_ttl"));
HIT_PARSER.declareLong(BasicHit::setTimestamp, new ParseField("_timestamp"));
HIT_PARSER.declareField((b, v) -> {}, p -> null, new ParseField("_score"), ValueType.FLOAT_OR_NULL);
HIT_PARSER.declareStringArray((b, v) -> {}, new ParseField("sort"));
}
/**
* Parser for the {@code hits} element. Parsed to an array of {@code [total (Long), hits (List<Hit>)]}.
*/
public static final ConstructingObjectParser<Object[], ParseFieldMatcherSupplier> HITS_PARSER = new ConstructingObjectParser<>("hits",
a -> a);
public static final ConstructingObjectParser<Object[], ParseFieldMatcherSupplier> HITS_PARSER =
new ConstructingObjectParser<>("hits", true, a -> a);
static {
HITS_PARSER.declareLong(constructorArg(), new ParseField("total"));
HITS_PARSER.declareObjectArray(constructorArg(), HIT_PARSER, new ParseField("hits"));
HITS_PARSER.declareField((b, v) -> {}, p -> null, new ParseField("max_score"), ValueType.FLOAT_OR_NULL);
}
/**
* Parser for {@code failed} shards in the {@code _shards} elements.
*/
public static final ConstructingObjectParser<SearchFailure, ParseFieldMatcherSupplier> SEARCH_FAILURE_PARSER =
new ConstructingObjectParser<>("failure", a -> {
new ConstructingObjectParser<>("failure", true, a -> {
int i = 0;
String index = (String) a[i++];
Integer shardId = (Integer) a[i++];
@ -135,7 +129,6 @@ final class RemoteResponseParsers {
return p.text();
}
}, new ParseField("reason"), ValueType.OBJECT_OR_STRING);
SEARCH_FAILURE_PARSER.declareInt((b, v) -> {}, new ParseField("status"));
}
/**
@ -143,7 +136,7 @@ final class RemoteResponseParsers {
* parses to an empty list.
*/
public static final ConstructingObjectParser<List<Throwable>, ParseFieldMatcherSupplier> SHARDS_PARSER =
new ConstructingObjectParser<>("_shards", a -> {
new ConstructingObjectParser<>("_shards", true, a -> {
@SuppressWarnings("unchecked")
List<Throwable> failures = (List<Throwable>) a[0];
failures = failures == null ? emptyList() : failures;
@ -151,13 +144,10 @@ final class RemoteResponseParsers {
});
static {
SHARDS_PARSER.declareObjectArray(optionalConstructorArg(), SEARCH_FAILURE_PARSER, new ParseField("failures"));
SHARDS_PARSER.declareInt((b, v) -> {}, new ParseField("total"));
SHARDS_PARSER.declareInt((b, v) -> {}, new ParseField("successful"));
SHARDS_PARSER.declareInt((b, v) -> {}, new ParseField("failed"));
}
public static final ConstructingObjectParser<Response, ParseFieldMatcherSupplier> RESPONSE_PARSER =
new ConstructingObjectParser<>("search_response", a -> {
new ConstructingObjectParser<>("search_response", true, a -> {
int i = 0;
Throwable catastrophicFailure = (Throwable) a[i++];
if (catastrophicFailure != null) {
@ -189,9 +179,6 @@ final class RemoteResponseParsers {
RESPONSE_PARSER.declareString(optionalConstructorArg(), new ParseField("_scroll_id"));
RESPONSE_PARSER.declareObject(optionalConstructorArg(), HITS_PARSER, new ParseField("hits"));
RESPONSE_PARSER.declareObject(optionalConstructorArg(), SHARDS_PARSER, new ParseField("_shards"));
RESPONSE_PARSER.declareInt((b, v) -> {}, new ParseField("took"));
RESPONSE_PARSER.declareBoolean((b, v) -> {}, new ParseField("terminated_early"));
RESPONSE_PARSER.declareInt((b, v) -> {}, new ParseField("status"));
}
/**
@ -200,7 +187,7 @@ final class RemoteResponseParsers {
public static class ThrowableBuilder {
public static final BiFunction<XContentParser, ParseFieldMatcherSupplier, Throwable> PARSER;
static {
ObjectParser<ThrowableBuilder, ParseFieldMatcherSupplier> parser = new ObjectParser<>("reason", ThrowableBuilder::new);
ObjectParser<ThrowableBuilder, ParseFieldMatcherSupplier> parser = new ObjectParser<>("reason", true, ThrowableBuilder::new);
PARSER = parser.andThen(ThrowableBuilder::build);
parser.declareString(ThrowableBuilder::setType, new ParseField("type"));
parser.declareString(ThrowableBuilder::setReason, new ParseField("reason"));
@ -209,14 +196,6 @@ final class RemoteResponseParsers {
// So we can give a nice error for parsing exceptions
parser.declareInt(ThrowableBuilder::setLine, new ParseField("line"));
parser.declareInt(ThrowableBuilder::setColumn, new ParseField("col"));
// So we don't blow up on search exceptions
parser.declareString((b, v) -> {}, new ParseField("phase"));
parser.declareBoolean((b, v) -> {}, new ParseField("grouped"));
parser.declareField((p, v, c) -> p.skipChildren(), new ParseField("failed_shards"), ValueType.OBJECT_ARRAY);
// Just throw away the root_cause
parser.declareField((p, v, c) -> p.skipChildren(), new ParseField("root_cause"), ValueType.OBJECT_ARRAY);
}
private String type;
@ -269,34 +248,15 @@ final class RemoteResponseParsers {
}
}
/**
* Parses the {@code version} field of the main action. There are a surprising number of fields in this that we don't need!
*/
public static final ConstructingObjectParser<Version, ParseFieldMatcherSupplier> VERSION_PARSER = new ConstructingObjectParser<>(
"version", a -> Version.fromString((String) a[0]));
static {
VERSION_PARSER.declareString(constructorArg(), new ParseField("number"));
VERSION_PARSER.declareBoolean((p, v) -> {}, new ParseField("snapshot_build"));
VERSION_PARSER.declareBoolean((p, v) -> {}, new ParseField("build_snapshot"));
VERSION_PARSER.declareString((p, v) -> {}, new ParseField("build_hash"));
VERSION_PARSER.declareString((p, v) -> {}, new ParseField("build_date"));
VERSION_PARSER.declareString((p, v) -> {}, new ParseField("build_timestamp"));
VERSION_PARSER.declareString((p, v) -> {}, new ParseField("lucene_version"));
}
/**
* Parses the main action to return just the {@linkplain Version} that it returns. We throw everything else out.
*/
public static final ConstructingObjectParser<Version, ParseFieldMatcherSupplier> MAIN_ACTION_PARSER = new ConstructingObjectParser<>(
"/", a -> (Version) a[0]);
"/", true, a -> (Version) a[0]);
static {
MAIN_ACTION_PARSER.declareBoolean((p, v) -> {}, new ParseField("ok"));
MAIN_ACTION_PARSER.declareInt((p, v) -> {}, new ParseField("status"));
MAIN_ACTION_PARSER.declareString((p, v) -> {}, new ParseField("name"));
MAIN_ACTION_PARSER.declareString((p, v) -> {}, new ParseField("cluster_name"));
MAIN_ACTION_PARSER.declareString((p, v) -> {}, new ParseField("cluster_uuid"));
MAIN_ACTION_PARSER.declareString((p, v) -> {}, new ParseField("name"));
MAIN_ACTION_PARSER.declareString((p, v) -> {}, new ParseField("tagline"));
MAIN_ACTION_PARSER.declareObject(constructorArg(), VERSION_PARSER, new ParseField("version"));
ConstructingObjectParser<Version, ParseFieldMatcherSupplier> versionParser = new ConstructingObjectParser<>(
"version", true, a -> Version.fromString((String) a[0]));
versionParser.declareString(constructorArg(), new ParseField("number"));
MAIN_ACTION_PARSER.declareObject(constructorArg(), versionParser, new ParseField("version"));
}
}

View File

@ -113,11 +113,42 @@ public class RemoteScrollableHitSourceTests extends ESTestCase {
}
public void testLookupRemoteVersion() throws Exception {
sourceWithMockedRemoteCall(false, "main/0_20_5.json").lookupRemoteVersion(v -> assertEquals(Version.fromString("0.20.5"), v));
sourceWithMockedRemoteCall(false, "main/0_90_13.json").lookupRemoteVersion(v -> assertEquals(Version.fromString("0.90.13"), v));
sourceWithMockedRemoteCall(false, "main/1_7_5.json").lookupRemoteVersion(v -> assertEquals(Version.fromString("1.7.5"), v));
sourceWithMockedRemoteCall(false, "main/2_3_3.json").lookupRemoteVersion(v -> assertEquals(Version.V_2_3_3, v));
sourceWithMockedRemoteCall(false, "main/5_0_0_alpha_3.json").lookupRemoteVersion(v -> assertEquals(Version.V_5_0_0_alpha3, v));
AtomicBoolean called = new AtomicBoolean();
sourceWithMockedRemoteCall(false, "main/0_20_5.json").lookupRemoteVersion(v -> {
assertEquals(Version.fromString("0.20.5"), v);
called.set(true);
});
assertTrue(called.get());
called.set(false);
sourceWithMockedRemoteCall(false, "main/0_90_13.json").lookupRemoteVersion(v -> {
assertEquals(Version.fromString("0.90.13"), v);
called.set(true);
});
assertTrue(called.get());
called.set(false);
sourceWithMockedRemoteCall(false, "main/1_7_5.json").lookupRemoteVersion(v -> {
assertEquals(Version.fromString("1.7.5"), v);
called.set(true);
});
assertTrue(called.get());
called.set(false);
sourceWithMockedRemoteCall(false, "main/2_3_3.json").lookupRemoteVersion(v -> {
assertEquals(Version.V_2_3_3, v);
called.set(true);
});
assertTrue(called.get());
called.set(false);
sourceWithMockedRemoteCall(false, "main/5_0_0_alpha_3.json").lookupRemoteVersion(v -> {
assertEquals(Version.V_5_0_0_alpha3, v);
called.set(true);
});
assertTrue(called.get());
called.set(false);
sourceWithMockedRemoteCall(false, "main/with_unknown_fields.json").lookupRemoteVersion(v -> {
assertEquals(Version.V_5_0_0_alpha3, v);
called.set(true);
});
assertTrue(called.get());
}
public void testParseStartOk() throws Exception {

View File

@ -0,0 +1,22 @@
{
"name" : "Crazy Node With Weird Stuff In The Response",
"cluster_name" : "distribution_run",
"cats": "knock things over",
"cake": "is tasty",
"version" : {
"number" : "5.0.0-alpha3",
"build_hash" : "42e092f",
"build_date" : "2016-05-26T16:55:45.405Z",
"build_snapshot" : true,
"lucene_version" : "6.0.0",
"blort_version" : "not even a valid version number, what are you going to do about it?"
},
"tagline" : "You Know, for Search",
"extra_object" : {
"stuff": "stuff"
},
"extra_array" : [
"stuff",
"more stuff"
]
}