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:
Tim Vernum 2017-04-27 20:26:10 +10:00 committed by GitHub
parent 8a8410b5ce
commit 65f90b25e0
2 changed files with 58 additions and 5 deletions

View File

@ -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);
}

View File

@ -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;
}
}