org.elasticsearch.xcontent.AbstractObjectParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch-x-content Show documentation
Show all versions of elasticsearch-x-content Show documentation
Elasticsearch subproject :libs:elasticsearch-x-content
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.xcontent;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.xcontent.ObjectParser.NamedObjectParser;
import org.elasticsearch.xcontent.ObjectParser.ValueType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Superclass for {@link ObjectParser} and {@link ConstructingObjectParser}. Defines most of the "declare" methods so they can be shared.
*/
public abstract class AbstractObjectParser {
protected AbstractObjectParser() {}
/**
* Declare some field. Usually it is easier to use {@link #declareString(BiConsumer, ParseField)} or
* {@link #declareObject(BiConsumer, ContextParser, ParseField)} rather than call this directly.
*/
public abstract void declareField(
BiConsumer consumer,
ContextParser parser,
ParseField parseField,
ValueType type
);
/**
* Declares a single named object.
*
*
*
* {
* "object_name": {
* "instance_name": { "field1": "value1", ... }
* }
* }
* }
*
*
*
* @param consumer
* sets the value once it has been parsed
* @param namedObjectParser
* parses the named object
* @param parseField
* the field to parse
*/
public abstract void declareNamedObject(
BiConsumer consumer,
NamedObjectParser namedObjectParser,
ParseField parseField
);
/**
* Declares named objects in the style of aggregations. These are named
* inside and object like this:
*
*
*
* {
* "aggregations": {
* "name_1": { "aggregation_type": {} },
* "name_2": { "aggregation_type": {} },
* "name_3": { "aggregation_type": {} }
* }
* }
* }
*
*
*
* 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 void declareNamedObjects(
BiConsumer> consumer,
NamedObjectParser namedObjectParser,
ParseField parseField
);
/**
* Declares named objects in the style of highlighting's field element.
* These are usually named inside and object like this:
*
*
*
* {
* "highlight": {
* "fields": { <------ this one
* "title": {},
* "body": {},
* "category": {}
* }
* }
* }
*
*
*
* but, when order is important, some may be written this way:
*
*
*
* {
* "highlight": {
* "fields": [ <------ this one
* {"title": {}},
* {"body": {}},
* {"category": {}}
* ]
* }
* }
*
*
*
* 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 void declareNamedObjects(
BiConsumer> consumer,
NamedObjectParser namedObjectParser,
Consumer orderedModeCallback,
ParseField parseField
);
public abstract String getName();
public void declareField(
BiConsumer consumer,
CheckedFunction parser,
ParseField parseField,
ValueType type
) {
if (parser == null) {
throw new IllegalArgumentException("[parser] is required");
}
declareField(consumer, (p, c) -> parser.apply(p), parseField, type);
}
public void declareObject(BiConsumer consumer, ContextParser objectParser, ParseField field) {
declareField(consumer, objectParser, field, ValueType.OBJECT);
}
/**
* Declare an object field that parses explicit {@code null}s in the json to a default value.
*/
public void declareObjectOrNull(
BiConsumer consumer,
ContextParser objectParser,
T nullValue,
ParseField field
) {
declareField(
consumer,
(p, c) -> p.currentToken() == XContentParser.Token.VALUE_NULL ? nullValue : objectParser.parse(p, c),
field,
ValueType.OBJECT_OR_NULL
);
}
public void declareFloat(BiConsumer consumer, ParseField field) {
// Using a method reference here angers some compilers
declareField(consumer, p -> p.floatValue(), field, ValueType.FLOAT);
}
/**
* Declare a float field that parses explicit {@code null}s in the json to a default value.
*/
public void declareFloatOrNull(BiConsumer consumer, float nullValue, ParseField field) {
declareField(
consumer,
p -> p.currentToken() == XContentParser.Token.VALUE_NULL ? nullValue : p.floatValue(),
field,
ValueType.FLOAT_OR_NULL
);
}
public void declareDouble(BiConsumer consumer, ParseField field) {
// Using a method reference here angers some compilers
declareField(consumer, p -> p.doubleValue(), field, ValueType.DOUBLE);
}
/**
* Declare a double field that parses explicit {@code null}s in the json to a default value.
*/
public void declareDoubleOrNull(BiConsumer consumer, double nullValue, ParseField field) {
declareField(
consumer,
p -> p.currentToken() == XContentParser.Token.VALUE_NULL ? nullValue : p.doubleValue(),
field,
ValueType.DOUBLE_OR_NULL
);
}
public void declareLong(BiConsumer consumer, ParseField field) {
// Using a method reference here angers some compilers
declareField(consumer, p -> p.longValue(), field, ValueType.LONG);
}
public void declareLongOrNull(BiConsumer consumer, long nullValue, ParseField field) {
// Using a method reference here angers some compilers
declareField(
consumer,
p -> p.currentToken() == XContentParser.Token.VALUE_NULL ? nullValue : p.longValue(),
field,
ValueType.LONG_OR_NULL
);
}
public void declareInt(BiConsumer consumer, ParseField field) {
// Using a method reference here angers some compilers
declareField(consumer, p -> p.intValue(), field, ValueType.INT);
}
/**
* Declare an integer field that parses explicit {@code null}s in the json to a default value.
*/
public void declareIntOrNull(BiConsumer consumer, int nullValue, ParseField field) {
declareField(
consumer,
p -> p.currentToken() == XContentParser.Token.VALUE_NULL ? nullValue : p.intValue(),
field,
ValueType.INT_OR_NULL
);
}
public void declareString(BiConsumer consumer, ParseField field) {
declareField(consumer, XContentParser::text, field, ValueType.STRING);
}
/**
* Declare a field of type {@code T} parsed from string and converted to {@code T} using provided function.
* Throws if the next token is not a string.
*/
public void declareString(BiConsumer consumer, Function fromStringFunction, ParseField field) {
declareField(consumer, p -> fromStringFunction.apply(p.text()), field, ValueType.STRING);
}
public void declareStringOrNull(BiConsumer consumer, ParseField field) {
declareField(
consumer,
(p) -> p.currentToken() == XContentParser.Token.VALUE_NULL ? null : p.text(),
field,
ValueType.STRING_OR_NULL
);
}
public void declareBoolean(BiConsumer consumer, ParseField field) {
declareField(consumer, XContentParser::booleanValue, field, ValueType.BOOLEAN);
}
public void declareObjectArray(BiConsumer> consumer, ContextParser objectParser, ParseField field) {
declareFieldArray(consumer, objectParser, field, ValueType.OBJECT_ARRAY);
}
/**
* like {@link #declareObjectArray(BiConsumer, ContextParser, ParseField)}, but can also handle single null values,
* in which case the consumer isn't called
*/
public void declareObjectArrayOrNull(
BiConsumer> consumer,
ContextParser objectParser,
ParseField field
) {
declareField(
(value, list) -> { if (list != null) consumer.accept(value, list); },
(p, c) -> p.currentToken() == XContentParser.Token.VALUE_NULL ? null : parseArray(p, c, objectParser),
field,
ValueType.OBJECT_ARRAY_OR_NULL
);
}
public void declareStringArray(BiConsumer> consumer, ParseField field) {
declareFieldArray(consumer, (p, c) -> p.text(), field, ValueType.STRING_ARRAY);
}
public void declareDoubleArray(BiConsumer> consumer, ParseField field) {
declareFieldArray(consumer, (p, c) -> p.doubleValue(), field, ValueType.DOUBLE_ARRAY);
}
public void declareFloatArray(BiConsumer> consumer, ParseField field) {
declareFieldArray(consumer, (p, c) -> p.floatValue(), field, ValueType.FLOAT_ARRAY);
}
public void declareLongArray(BiConsumer> consumer, ParseField field) {
declareFieldArray(consumer, (p, c) -> p.longValue(), field, ValueType.LONG_ARRAY);
}
public void declareIntArray(BiConsumer> consumer, ParseField field) {
declareFieldArray(consumer, (p, c) -> p.intValue(), field, ValueType.INT_ARRAY);
}
/**
* Declares a field that can contain an array of elements listed in the type ValueType enum
*/
public void declareFieldArray(
BiConsumer> consumer,
ContextParser itemParser,
ParseField field,
ValueType type
) {
declareField(consumer, (p, c) -> parseArray(p, c, itemParser), field, type);
}
/**
* Declares a set of fields that are required for parsing to succeed. Only one of the values
* provided per String[] must be matched.
*
* E.g. declareRequiredFieldSet("foo", "bar");
means at least one of "foo" or
* "bar" fields must be present. If neither of those fields are present, an exception will be thrown.
*
* Multiple required sets can be configured:
*
*
* parser.declareRequiredFieldSet("foo", "bar");
* parser.declareRequiredFieldSet("bizz", "buzz");
*
*
* requires that one of "foo" or "bar" fields are present, and also that one of "bizz" or
* "buzz" fields are present.
*
* In JSON, it means any of these combinations are acceptable:
*
*
* {"foo":"...", "bizz": "..."}
* {"bar":"...", "bizz": "..."}
* {"foo":"...", "buzz": "..."}
* {"bar":"...", "buzz": "..."}
* {"foo":"...", "bar":"...", "bizz": "..."}
* {"foo":"...", "bar":"...", "buzz": "..."}
* {"foo":"...", "bizz":"...", "buzz": "..."}
* {"bar":"...", "bizz":"...", "buzz": "..."}
* {"foo":"...", "bar":"...", "bizz": "...", "buzz": "..."}
*
*
* The following would however be rejected:
*
*
* failure cases
* Provided JSON Reason for failure
* {"foo":"..."}
Missing "bizz" or "buzz" field
* {"bar":"..."}
Missing "bizz" or "buzz" field
* {"bizz": "..."}
Missing "foo" or "bar" field
* {"buzz": "..."}
Missing "foo" or "bar" field
* {"foo":"...", "bar": "..."}
Missing "bizz" or "buzz" field
* {"bizz":"...", "buzz": "..."}
Missing "foo" or "bar" field
* {"unrelated":"..."}
Missing "foo" or "bar" field, and missing "bizz" or "buzz" field
*
*
* @param requiredSet
* A set of required fields, where at least one of the fields in the array _must_ be present
*/
public abstract void declareRequiredFieldSet(String... requiredSet);
/**
* Declares a set of fields of which at most one must appear for parsing to succeed
*
* E.g. declareExclusiveFieldSet("foo", "bar");
means that only one of 'foo'
* or 'bar' must be present, and if both appear then an exception will be thrown. Note
* that this does not make 'foo' or 'bar' required - see {@link #declareRequiredFieldSet(String...)}
* for required fields.
*
* Multiple exclusive sets may be declared
*
* @param exclusiveSet a set of field names, at most one of which must appear
*/
public abstract void declareExclusiveFieldSet(String... exclusiveSet);
public static List parseArray(XContentParser parser, Context context, ContextParser itemParser)
throws IOException {
final XContentParser.Token currentToken = parser.currentToken();
if (currentToken.isValue()
|| currentToken == XContentParser.Token.VALUE_NULL
|| currentToken == XContentParser.Token.START_OBJECT) {
return Collections.singletonList(itemParser.parse(parser, context)); // single value
}
final List list = new ArrayList<>();
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token.isValue() || token == XContentParser.Token.VALUE_NULL || token == XContentParser.Token.START_OBJECT) {
list.add(itemParser.parse(parser, context));
} else {
throw new IllegalStateException("expected value but got [" + token + "]");
}
}
return list;
}
}