Adds declareNamedObjects methods to ConstructingObjectParser (#24219)
* Adds declareNamedObjects methods to ConstructingObjectParser * Addresses review comments
This commit is contained in:
parent
2b8fa64cf7
commit
3c7c4bc824
|
@ -22,6 +22,7 @@ package org.elasticsearch.common.xcontent;
|
|||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
|
||||
|
@ -30,6 +31,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
|
||||
|
@ -44,6 +46,94 @@ public abstract class AbstractObjectParser<Value, Context>
|
|||
public abstract <T> void declareField(BiConsumer<Value, T> consumer, ContextParser<Context, T> parser, ParseField parseField,
|
||||
ValueType type);
|
||||
|
||||
/**
|
||||
* Declares named objects in the style of aggregations. These are named
|
||||
* inside and object like this:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "aggregations": {
|
||||
* "name_1": { "aggregation_type": {} },
|
||||
* "name_2": { "aggregation_type": {} },
|
||||
* "name_3": { "aggregation_type": {} }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* Unlike the other version of this method, "ordered" mode (arrays of
|
||||
* objects) is not supported.
|
||||
*
|
||||
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke
|
||||
* this.
|
||||
*
|
||||
* @param consumer
|
||||
* sets the values once they have been parsed
|
||||
* @param namedObjectParser
|
||||
* parses each named object
|
||||
* @param parseField
|
||||
* the field to parse
|
||||
*/
|
||||
public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
ParseField parseField);
|
||||
|
||||
/**
|
||||
* Declares named objects in the style of highlighting's field element.
|
||||
* These are usually named inside and object like this:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "highlight": {
|
||||
* "fields": { <------ this one
|
||||
* "title": {},
|
||||
* "body": {},
|
||||
* "category": {}
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* but, when order is important, some may be written this way:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "highlight": {
|
||||
* "fields": [ <------ this one
|
||||
* {"title": {}},
|
||||
* {"body": {}},
|
||||
* {"category": {}}
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* This is because json doesn't enforce ordering. Elasticsearch reads it in
|
||||
* the order sent but tools that generate json are free to put object
|
||||
* members in an unordered Map, jumbling them. Thus, if you care about order
|
||||
* you can send the object in the second way.
|
||||
*
|
||||
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke
|
||||
* this.
|
||||
*
|
||||
* @param consumer
|
||||
* sets the values once they have been parsed
|
||||
* @param namedObjectParser
|
||||
* parses each named object
|
||||
* @param orderedModeCallback
|
||||
* called when the named object is parsed using the "ordered"
|
||||
* mode (the array of objects)
|
||||
* @param parseField
|
||||
* the field to parse
|
||||
*/
|
||||
public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
Consumer<Value> orderedModeCallback, ParseField parseField);
|
||||
|
||||
public <T> void declareField(BiConsumer<Value, T> consumer, CheckedFunction<XContentParser, T, IOException> parser,
|
||||
ParseField parseField, ValueType type) {
|
||||
if (parser == null) {
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.common.xcontent;
|
|||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -77,14 +78,14 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
/**
|
||||
* Consumer that marks a field as a required constructor argument instead of a real object field.
|
||||
*/
|
||||
private static final BiConsumer<Object, Object> REQUIRED_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
|
||||
private static final BiConsumer<?, ?> REQUIRED_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
|
||||
throw new UnsupportedOperationException("I am just a marker I should never be called.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Consumer that marks a field as an optional constructor argument instead of a real object field.
|
||||
*/
|
||||
private static final BiConsumer<Object, Object> OPTIONAL_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
|
||||
private static final BiConsumer<?, ?> OPTIONAL_CONSTRUCTOR_ARG_MARKER = (a, b) -> {
|
||||
throw new UnsupportedOperationException("I am just a marker I should never be called.");
|
||||
};
|
||||
|
||||
|
@ -189,7 +190,7 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
|
||||
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
|
||||
* Constructor arguments are detected by these "marker" consumers. It keeps the API looking clean even if it is a bit sleezy. We
|
||||
* then build a new consumer directly against the object parser that triggers the "constructor arg just arrived behavior" of the
|
||||
* parser. Conveniently, we can close over the position of the constructor in the argument list so we don't need to do any fancy
|
||||
* or expensive lookups whenever the constructor args come in.
|
||||
|
@ -204,6 +205,91 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
ParseField parseField) {
|
||||
if (consumer == null) {
|
||||
throw new IllegalArgumentException("[consumer] is required");
|
||||
}
|
||||
if (namedObjectParser == null) {
|
||||
throw new IllegalArgumentException("[parser] is required");
|
||||
}
|
||||
if (parseField == null) {
|
||||
throw new IllegalArgumentException("[parseField] 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 then
|
||||
* build a new consumer directly against the object parser that
|
||||
* triggers the "constructor arg just arrived behavior" of the
|
||||
* parser. Conveniently, we can close over the position of the
|
||||
* constructor in the argument list so we don't need to do any fancy
|
||||
* or expensive lookups whenever the constructor args come in.
|
||||
*/
|
||||
int position = constructorArgInfos.size();
|
||||
boolean required = consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER;
|
||||
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
|
||||
objectParser.declareNamedObjects((target, v) -> target.constructorArg(position, parseField, v), namedObjectParser, parseField);
|
||||
} else {
|
||||
numberOfFields += 1;
|
||||
objectParser.declareNamedObjects(queueingConsumer(consumer, parseField), namedObjectParser, parseField);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
Consumer<Value> orderedModeCallback, ParseField parseField) {
|
||||
if (consumer == null) {
|
||||
throw new IllegalArgumentException("[consumer] is required");
|
||||
}
|
||||
if (namedObjectParser == null) {
|
||||
throw new IllegalArgumentException("[parser] is required");
|
||||
}
|
||||
if (orderedModeCallback == null) {
|
||||
throw new IllegalArgumentException("[orderedModeCallback] is required");
|
||||
}
|
||||
if (parseField == null) {
|
||||
throw new IllegalArgumentException("[parseField] 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 then
|
||||
* build a new consumer directly against the object parser that
|
||||
* triggers the "constructor arg just arrived behavior" of the
|
||||
* parser. Conveniently, we can close over the position of the
|
||||
* constructor in the argument list so we don't need to do any fancy
|
||||
* or expensive lookups whenever the constructor args come in.
|
||||
*/
|
||||
int position = constructorArgInfos.size();
|
||||
boolean required = consumer == REQUIRED_CONSTRUCTOR_ARG_MARKER;
|
||||
constructorArgInfos.add(new ConstructorArgInfo(parseField, required));
|
||||
objectParser.declareNamedObjects((target, v) -> target.constructorArg(position, parseField, v), namedObjectParser,
|
||||
wrapOrderedModeCallBack(orderedModeCallback), parseField);
|
||||
} else {
|
||||
numberOfFields += 1;
|
||||
objectParser.declareNamedObjects(queueingConsumer(consumer, parseField), namedObjectParser,
|
||||
wrapOrderedModeCallBack(orderedModeCallback), parseField);
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<Target> wrapOrderedModeCallBack(Consumer<Value> callback) {
|
||||
return (target) -> {
|
||||
if (target.targetObject != null) {
|
||||
// The target has already been built. Call the callback now.
|
||||
callback.accept(target.targetObject);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* The target hasn't been built. Queue the callback.
|
||||
*/
|
||||
target.queuedOrderedModeCallback = callback;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the consumer that does the "field just arrived" behavior. If the targetObject hasn't been built then it queues the value.
|
||||
* Otherwise it just applies the value just like {@linkplain ObjectParser} does.
|
||||
|
@ -258,6 +344,11 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
* Fields to be saved to the target object when we can build it. This is only allocated if a field has to be queued.
|
||||
*/
|
||||
private Consumer<Value>[] queuedFields;
|
||||
/**
|
||||
* OrderedModeCallback to be called with the target object when we can
|
||||
* build it. This is only allocated if the callback has to be queued.
|
||||
*/
|
||||
private Consumer<Value> queuedOrderedModeCallback;
|
||||
/**
|
||||
* The count of fields already queued.
|
||||
*/
|
||||
|
@ -343,6 +434,9 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
private void buildTarget() {
|
||||
try {
|
||||
targetObject = builder.apply(constructorArgs);
|
||||
if (queuedOrderedModeCallback != null) {
|
||||
queuedOrderedModeCallback.accept(targetObject);
|
||||
}
|
||||
while (queuedFieldsCount > 0) {
|
||||
queuedFieldsCount -= 1;
|
||||
queuedFields[queuedFieldsCount].accept(targetObject);
|
||||
|
|
|
@ -227,41 +227,7 @@ public final class ObjectParser<Value, Context> extends AbstractObjectParser<Val
|
|||
}, field, ValueType.OBJECT_OR_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares named objects in the style of highlighting's field element. These are usually named inside and object like this:
|
||||
* <pre><code>
|
||||
* {
|
||||
* "highlight": {
|
||||
* "fields": { <------ this one
|
||||
* "title": {},
|
||||
* "body": {},
|
||||
* "category": {}
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* but, when order is important, some may be written this way:
|
||||
* <pre><code>
|
||||
* {
|
||||
* "highlight": {
|
||||
* "fields": [ <------ this one
|
||||
* {"title": {}},
|
||||
* {"body": {}},
|
||||
* {"category": {}}
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* This is because json doesn't enforce ordering. Elasticsearch reads it in the order sent but tools that generate json are free to put
|
||||
* object members in an unordered Map, jumbling them. Thus, if you care about order you can send the object in the second way.
|
||||
*
|
||||
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke this.
|
||||
*
|
||||
* @param consumer sets the values once they have been parsed
|
||||
* @param namedObjectParser parses each named object
|
||||
* @param orderedModeCallback called when the named object is parsed using the "ordered" mode (the array of objects)
|
||||
* @param field the field to parse
|
||||
*/
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
Consumer<Value> orderedModeCallback, ParseField field) {
|
||||
// This creates and parses the named object
|
||||
|
@ -311,26 +277,7 @@ public final class ObjectParser<Value, Context> extends AbstractObjectParser<Val
|
|||
}, field, ValueType.OBJECT_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares named objects in the style of aggregations. These are named inside and object like this:
|
||||
* <pre><code>
|
||||
* {
|
||||
* "aggregations": {
|
||||
* "name_1": { "aggregation_type": {} },
|
||||
* "name_2": { "aggregation_type": {} },
|
||||
* "name_3": { "aggregation_type": {} }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* Unlike the other version of this method, "ordered" mode (arrays of objects) is not supported.
|
||||
*
|
||||
* See NamedObjectHolder in ObjectParserTests for examples of how to invoke this.
|
||||
*
|
||||
* @param consumer sets the values once they have been parsed
|
||||
* @param namedObjectParser parses each named object
|
||||
* @param field the field to parse
|
||||
*/
|
||||
@Override
|
||||
public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
|
||||
ParseField field) {
|
||||
Consumer<Value> orderedModeCallback = (v) -> {
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.common.Nullable;
|
|||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.ObjectParserTests.NamedObject;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Matcher;
|
||||
|
@ -37,6 +38,7 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
|
|||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
|
@ -397,4 +399,188 @@ public class ConstructingObjectParserTests extends ESTestCase {
|
|||
return parser;
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseNamedObject() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": {\n"
|
||||
+ " \"a\": {}"
|
||||
+ "},\"named_in_constructor\": {\n"
|
||||
+ " \"b\": {}"
|
||||
+ "}}");
|
||||
NamedObjectHolder h = NamedObjectHolder.PARSER.apply(parser, null);
|
||||
assertThat(h.named, hasSize(1));
|
||||
assertEquals("a", h.named.get(0).name);
|
||||
assertThat(h.namedInConstructor, hasSize(1));
|
||||
assertEquals("b", h.namedInConstructor.get(0).name);
|
||||
assertFalse(h.namedSuppliedInOrder);
|
||||
}
|
||||
|
||||
public void testParseNamedObjectInOrder() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {\"b\": {}}"
|
||||
+ "]}");
|
||||
NamedObjectHolder h = NamedObjectHolder.PARSER.apply(parser, null);
|
||||
assertThat(h.named, hasSize(1));
|
||||
assertEquals("a", h.named.get(0).name);
|
||||
assertThat(h.namedInConstructor, hasSize(1));
|
||||
assertEquals("b", h.namedInConstructor.get(0).name);
|
||||
assertTrue(h.namedSuppliedInOrder);
|
||||
}
|
||||
|
||||
public void testParseNamedObjectTwoFieldsInArray() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}, \"b\": {}}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {\"c\": {}}"
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
|
||||
e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectTwoFieldsInArrayConstructorArg() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {\"c\": {}, \"d\": {}}"
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
|
||||
+ "single field", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectNoFieldsInArray() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
|
||||
e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectNoFieldsInArrayConstructorArg() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {}"
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
|
||||
+ "single field", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectJunkInArray() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " \"junk\""
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named] can be a single object with any number of fields or an array where each entry is an object with a single field",
|
||||
e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectJunkInArrayConstructorArg() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "],\"named_in_constructor\": [\n"
|
||||
+ " \"junk\""
|
||||
+ "]}");
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> NamedObjectHolder.PARSER.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
|
||||
assertEquals(
|
||||
"[named_in_constructor] can be a single object with any number of fields or an array where each entry is an object with a "
|
||||
+ "single field", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectInOrderNotSupported() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": [\n"
|
||||
+ " {\"a\": {}}"
|
||||
+ "],\"named_in_constructor\": {\"b\": {}}"
|
||||
+ "}");
|
||||
|
||||
// Create our own parser for this test so we can disable support for the "ordered" mode specified by the array above
|
||||
@SuppressWarnings("unchecked")
|
||||
ConstructingObjectParser<NamedObjectHolder, Void> objectParser = new ConstructingObjectParser<>("named_object_holder",
|
||||
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
|
||||
objectParser.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER,
|
||||
new ParseField("named_in_constructor"));
|
||||
objectParser.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, new ParseField("named"));
|
||||
|
||||
// Now firing the xml through it fails
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> objectParser.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named]", e.getMessage());
|
||||
assertEquals("[named] doesn't support arrays. Use a single object with multiple fields.", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
public void testParseNamedObjectInOrderNotSupportedConstructorArg() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\"named\": {\"a\": {}}"
|
||||
+ ",\"named_in_constructor\": [\n"
|
||||
+ " {\"b\": {}}"
|
||||
+ "]}");
|
||||
|
||||
// Create our own parser for this test so we can disable support for the "ordered" mode specified by the array above
|
||||
@SuppressWarnings("unchecked")
|
||||
ConstructingObjectParser<NamedObjectHolder, Void> objectParser = new ConstructingObjectParser<>("named_object_holder",
|
||||
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
|
||||
objectParser.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER,
|
||||
new ParseField("named_in_constructor"));
|
||||
objectParser.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, new ParseField("named"));
|
||||
|
||||
// Now firing the xml through it fails
|
||||
ParsingException e = expectThrows(ParsingException.class, () -> objectParser.apply(parser, null));
|
||||
assertEquals("[named_object_holder] failed to parse field [named_in_constructor]", e.getMessage());
|
||||
assertEquals("[named_in_constructor] doesn't support arrays. Use a single object with multiple fields.", e.getCause().getMessage());
|
||||
}
|
||||
|
||||
static class NamedObjectHolder {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final ConstructingObjectParser<NamedObjectHolder, Void> PARSER = new ConstructingObjectParser<>("named_object_holder",
|
||||
a -> new NamedObjectHolder(((List<NamedObject>) a[0])));
|
||||
static {
|
||||
PARSER.declareNamedObjects(ConstructingObjectParser.constructorArg(), NamedObject.PARSER, NamedObjectHolder::keepNamedInOrder,
|
||||
new ParseField("named_in_constructor"));
|
||||
PARSER.declareNamedObjects(NamedObjectHolder::setNamed, NamedObject.PARSER, NamedObjectHolder::keepNamedInOrder,
|
||||
new ParseField("named"));
|
||||
}
|
||||
|
||||
private List<NamedObject> named;
|
||||
private List<NamedObject> namedInConstructor;
|
||||
private boolean namedSuppliedInOrder = false;
|
||||
|
||||
NamedObjectHolder(List<NamedObject> namedInConstructor) {
|
||||
this.namedInConstructor = namedInConstructor;
|
||||
}
|
||||
|
||||
public void setNamed(List<NamedObject> named) {
|
||||
this.named = named;
|
||||
}
|
||||
|
||||
public void keepNamedInOrder() {
|
||||
namedSuppliedInOrder = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue