Pass Context to ConstructingObjectParser's function (#24230)
Allow the `Context` to be used in the builder function used within ConstructingObjectParser. This facilitates scenarios where a constructor argument comes from a URL parameter, or from document id.
This commit is contained in:
parent
8a8410b5ce
commit
65f90b25e0
|
@ -94,7 +94,7 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
*/
|
||||
private final List<ConstructorArgInfo> constructorArgInfos = new ArrayList<>();
|
||||
private final ObjectParser<Target, Context> objectParser;
|
||||
private final Function<Object[], Value> builder;
|
||||
private final BiFunction<Object[], Context, Value> builder;
|
||||
/**
|
||||
* The number of fields on the targetObject. This doesn't include any constructor arguments and is the size used for the array backing
|
||||
* the field queue.
|
||||
|
@ -130,8 +130,27 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
* allocations.
|
||||
*/
|
||||
public ConstructingObjectParser(String name, boolean ignoreUnknownFields, Function<Object[], Value> builder) {
|
||||
this(name, ignoreUnknownFields, (args, context) -> builder.apply(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 binary function that builds the object from an array of Objects and the parser context. 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. The second
|
||||
* argument is the value of the context provided to the {@link #parse(XContentParser, Object) parse function}. 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, BiFunction<Object[], Context, Value> builder) {
|
||||
objectParser = new ObjectParser<>(name, ignoreUnknownFields, null);
|
||||
this.builder = builder;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +167,7 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
|
||||
@Override
|
||||
public Value parse(XContentParser parser, Context context) throws IOException {
|
||||
return objectParser.parse(parser, new Target(parser), context).finish();
|
||||
return objectParser.parse(parser, new Target(parser, context), context).finish();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,6 +353,12 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
* location of each field so that we can give a useful error message when replaying the queue.
|
||||
*/
|
||||
private final XContentParser parser;
|
||||
|
||||
/**
|
||||
* The parse context that is used for this invocation. Stored here so that it can be passed to the {@link #builder}.
|
||||
*/
|
||||
private Context context;
|
||||
|
||||
/**
|
||||
* How many of the constructor parameters have we collected? We keep track of this so we don't have to count the
|
||||
* {@link #constructorArgs} array looking for nulls when we receive another constructor parameter. When this is equal to the size of
|
||||
|
@ -358,8 +383,9 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
*/
|
||||
private Value targetObject;
|
||||
|
||||
Target(XContentParser parser) {
|
||||
Target(XContentParser parser, Context context) {
|
||||
this.parser = parser;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,7 +459,7 @@ public final class ConstructingObjectParser<Value, Context> extends AbstractObje
|
|||
|
||||
private void buildTarget() {
|
||||
try {
|
||||
targetObject = builder.apply(constructorArgs);
|
||||
targetObject = builder.apply(constructorArgs, context);
|
||||
if (queuedOrderedModeCallback != null) {
|
||||
queuedOrderedModeCallback.accept(targetObject);
|
||||
}
|
||||
|
|
|
@ -303,6 +303,18 @@ public class ConstructingObjectParserTests extends ESTestCase {
|
|||
assertEquals(s.test, "foo");
|
||||
}
|
||||
|
||||
public void testConstructObjectUsingContext() throws IOException {
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent,
|
||||
"{\n"
|
||||
+ " \"animal\": \"dropbear\",\n"
|
||||
+ " \"mineral\": -8\n"
|
||||
+ "}");
|
||||
HasCtorArguments parsed = HasCtorArguments.PARSER_INT_CONTEXT.apply(parser, 42);
|
||||
assertEquals(Integer.valueOf(42), parsed.vegetable);
|
||||
assertEquals("dropbear", parsed.animal);
|
||||
assertEquals(-8, parsed.mineral);
|
||||
}
|
||||
|
||||
private static class HasCtorArguments implements ToXContent {
|
||||
@Nullable
|
||||
final String animal;
|
||||
|
@ -381,22 +393,37 @@ public class ConstructingObjectParserTests extends ESTestCase {
|
|||
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER = buildParser(true, true);
|
||||
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER_VEGETABLE_OPTIONAL = buildParser(true, false);
|
||||
public static final ConstructingObjectParser<HasCtorArguments, Void> PARSER_ALL_OPTIONAL = buildParser(false, false);
|
||||
|
||||
public static final List<ConstructingObjectParser<HasCtorArguments, Void>> ALL_PARSERS = unmodifiableList(
|
||||
Arrays.asList(PARSER, PARSER_VEGETABLE_OPTIONAL, PARSER_ALL_OPTIONAL));
|
||||
|
||||
public static final ConstructingObjectParser<HasCtorArguments, Integer> PARSER_INT_CONTEXT = buildContextParser();
|
||||
|
||||
private static ConstructingObjectParser<HasCtorArguments, Void> buildParser(boolean animalRequired,
|
||||
boolean vegetableRequired) {
|
||||
ConstructingObjectParser<HasCtorArguments, Void> parser = new ConstructingObjectParser<>(
|
||||
"has_required_arguments", a -> new HasCtorArguments((String) a[0], (Integer) a[1]));
|
||||
parser.declareString(animalRequired ? constructorArg() : optionalConstructorArg(), new ParseField("animal"));
|
||||
parser.declareInt(vegetableRequired ? constructorArg() : optionalConstructorArg(), new ParseField("vegetable"));
|
||||
declareSetters(parser);
|
||||
return parser;
|
||||
}
|
||||
|
||||
private static ConstructingObjectParser<HasCtorArguments, Integer> buildContextParser() {
|
||||
ConstructingObjectParser<HasCtorArguments, Integer> parser = new ConstructingObjectParser<>(
|
||||
"has_required_arguments", false, (args, ctx) -> new HasCtorArguments((String) args[0], ctx));
|
||||
parser.declareString(constructorArg(), new ParseField("animal"));
|
||||
declareSetters(parser);
|
||||
return parser;
|
||||
}
|
||||
|
||||
private static void declareSetters(ConstructingObjectParser<HasCtorArguments, ?> parser) {
|
||||
parser.declareInt(HasCtorArguments::setMineral, new ParseField("mineral"));
|
||||
parser.declareInt(HasCtorArguments::setFruit, new ParseField("fruit"));
|
||||
parser.declareString(HasCtorArguments::setA, new ParseField("a"));
|
||||
parser.declareString(HasCtorArguments::setB, new ParseField("b"));
|
||||
parser.declareString(HasCtorArguments::setC, new ParseField("c"));
|
||||
parser.declareBoolean(HasCtorArguments::setD, new ParseField("d"));
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue