Skip to content

Commit 75dbfec

Browse files
authored
Enable InstantiatingObjectParser to pass context as a first argument (#79206)
This is a followup for #78790, which allows us to replace ConstructingObjectParser with InstantiatingObjectParser which makes keeping track of the positional arguments somewhat easier.
1 parent 69defdb commit 75dbfec

File tree

3 files changed

+208
-52
lines changed

3 files changed

+208
-52
lines changed

libs/x-content/src/main/java/org/elasticsearch/xcontent/InstantiatingObjectParser.java

+52-14
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@
2323
* <p>
2424
* The main differences being that it is using Builder to construct the parser and takes a class of the target object instead of the object
2525
* builder. The target object must have exactly one constructor with the number and order of arguments matching the number of order of
26-
* declared fields. If there are more then 2 constructors with the same number of arguments, one of them needs to be marked with
26+
* declared fields. If there are more than 2 constructors with the same number of arguments, one of them needs to be marked with
2727
* {@linkplain ParserConstructor} annotation.
28+
*
29+
* It is also possible for the constructor to accept Context as the first parameter, in this case as in the case with multiple constructors
30+
* it is required for the constructor to be marked with {@linkplain ParserConstructor} annotation.
31+
*
2832
* <pre>{@code
2933
* public static class Thing{
3034
* public Thing(String animal, String vegetable, int mineral) {
@@ -37,14 +41,35 @@
3741
*
3842
* }
3943
*
40-
* private static final InstantiatingObjectParser<Thing, SomeContext> PARSER = new InstantiatingObjectParser<>("thing", Thing.class);
44+
* private static final InstantiatingObjectParser<Thing, SomeContext> PARSER;
45+
* static {
46+
* InstantiatingObjectParser.Builder<Thing, SomeContext> parser =
47+
* InstantiatingObjectParser,builder<>("thing", true, Thing.class);
48+
* parser.declareString(constructorArg(), new ParseField("animal"));
49+
* parser.declareString(constructorArg(), new ParseField("vegetable"));
50+
* parser.declareInt(optionalConstructorArg(), new ParseField("mineral"));
51+
* parser.declareInt(Thing::setFruit, new ParseField("fruit"));
52+
* parser.declareInt(Thing::setBug, new ParseField("bug"));
53+
* PARSER = parser.build()
54+
* }
55+
* }</pre>
56+
* <pre>{@code
57+
*
58+
* public static class AnotherThing {
59+
* @ParserConstructor
60+
* public AnotherThing(SomeContext continent, String animal, String vegetable, int mineral) {
61+
* ....
62+
* }
63+
* }
64+
*
65+
* private static final InstantiatingObjectParser<AnotherThing, SomeContext> PARSER;
4166
* static {
42-
* PARSER.declareString(constructorArg(), new ParseField("animal"));
43-
* PARSER.declareString(constructorArg(), new ParseField("vegetable"));
44-
* PARSER.declareInt(optionalConstructorArg(), new ParseField("mineral"));
45-
* PARSER.declareInt(Thing::setFruit, new ParseField("fruit"));
46-
* PARSER.declareInt(Thing::setBug, new ParseField("bug"));
47-
* PARSER.finalizeFields()
67+
* InstantiatingObjectParser.Builder<AnotherThing, SomeContext> parser =
68+
* InstantiatingObjectParser,builder<>("thing", true, AnotherThing.class);
69+
* parser.declareString(constructorArg(), new ParseField("animal"));
70+
* parser.declareString(constructorArg(), new ParseField("vegetable"));
71+
* parser.declareInt(optionalConstructorArg(), new ParseField("mineral"));
72+
* PARSER = parser.build()
4873
* }
4974
* }</pre>
5075
*/
@@ -72,7 +97,7 @@ public Builder(String name, Class<Value> valueClass) {
7297
}
7398

7499
public Builder(String name, boolean ignoreUnknownFields, Class<Value> valueClass) {
75-
this.constructingObjectParser = new ConstructingObjectParser<>(name, ignoreUnknownFields, this::build);
100+
this.constructingObjectParser = new ConstructingObjectParser<>(name, ignoreUnknownFields, this::buildInstance);
76101
this.valueClass = valueClass;
77102
}
78103

@@ -87,9 +112,15 @@ public InstantiatingObjectParser<Value, Context> build() {
87112
throw new IllegalArgumentException("More then one public constructor with @ParserConstructor annotation exist in " +
88113
"the class " + valueClass.getName());
89114
}
90-
if (c.getParameterCount() != neededArguments) {
91-
throw new IllegalArgumentException("Annotated constructor doesn't have " + neededArguments +
92-
" arguments in the class " + valueClass.getName());
115+
if (c.getParameterCount() < neededArguments || c.getParameterCount() > neededArguments + 1) {
116+
throw new IllegalArgumentException(
117+
"Annotated constructor doesn't have "
118+
+ neededArguments
119+
+ " or "
120+
+ (neededArguments + 1)
121+
+ " arguments in the class "
122+
+ valueClass.getName()
123+
);
93124
}
94125
constructor = c;
95126
}
@@ -154,13 +185,20 @@ public void declareExclusiveFieldSet(String... exclusiveSet) {
154185
constructingObjectParser.declareExclusiveFieldSet(exclusiveSet);
155186
}
156187

157-
private Value build(Object[] args) {
188+
private Value buildInstance(Object[] args, Context context) {
158189
if (constructor == null) {
159190
throw new IllegalArgumentException("InstantiatingObjectParser for type " + valueClass.getName() + " has to be finalized " +
160191
"before the first use");
161192
}
162193
try {
163-
return constructor.newInstance(args);
194+
if (constructor.getParameterCount() != args.length) {
195+
Object[] newArgs = new Object[args.length + 1];
196+
System.arraycopy(args, 0, newArgs, 1, args.length);
197+
newArgs[0] = context;
198+
return constructor.newInstance(newArgs);
199+
} else {
200+
return constructor.newInstance(args);
201+
}
164202
} catch (Exception ex) {
165203
throw new IllegalArgumentException("Cannot instantiate an object of " + valueClass.getName(), ex);
166204
}

libs/x-content/src/test/java/org/elasticsearch/xcontent/InstantiatingObjectParserTests.java

+80-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@
88

99
package org.elasticsearch.xcontent;
1010

11-
import org.elasticsearch.xcontent.InstantiatingObjectParser;
12-
import org.elasticsearch.xcontent.ParseField;
13-
import org.elasticsearch.xcontent.ParserConstructor;
14-
import org.elasticsearch.xcontent.json.JsonXContent;
1511
import org.elasticsearch.test.ESTestCase;
12+
import org.elasticsearch.xcontent.json.JsonXContent;
1613

1714
import java.io.IOException;
1815
import java.util.Objects;
@@ -217,8 +214,10 @@ public void testAnnotationWrongArgumentNumber() {
217214
InstantiatingObjectParser.Builder<Annotations, Void> builder = InstantiatingObjectParser.builder("foo", Annotations.class);
218215
builder.declareInt(constructorArg(), new ParseField("a"));
219216
builder.declareString(constructorArg(), new ParseField("b"));
217+
builder.declareInt(constructorArg(), new ParseField("c"));
218+
builder.declareString(constructorArg(), new ParseField("d"));
220219
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
221-
assertThat(e.getMessage(), containsString("Annotated constructor doesn't have 2 arguments in the class"));
220+
assertThat(e.getMessage(), containsString("Annotated constructor doesn't have 4 or 5 arguments in the class"));
222221
}
223222

224223
public void testDoubleDeclarationThrowsException() throws IOException {
@@ -240,4 +239,80 @@ class DoubleFieldDeclaration {
240239
assertThat(exception, instanceOf(IllegalArgumentException.class));
241240
assertThat(exception.getMessage(), startsWith("Parser already registered for name=[name]"));
242241
}
242+
243+
public static class ContextArgument {
244+
final String context;
245+
final int a;
246+
final String b;
247+
final long c;
248+
249+
public ContextArgument() {
250+
this(1, "2", 3);
251+
}
252+
253+
public ContextArgument(int a, String b) {
254+
this(a, b, -1);
255+
}
256+
257+
258+
public ContextArgument(int a, String b, long c) {
259+
this(null, a, b, c);
260+
}
261+
262+
public ContextArgument(String context, int a, String b, long c) {
263+
this.context = context;
264+
this.a = a;
265+
this.b = b;
266+
this.c = c;
267+
}
268+
269+
@ParserConstructor
270+
public ContextArgument(String context, int a, String b, String c) {
271+
this.context = context;
272+
this.a = a;
273+
this.b = b;
274+
this.c = Long.parseLong(c);
275+
}
276+
277+
@Override
278+
public boolean equals(Object o) {
279+
if (this == o) return true;
280+
if (o == null || getClass() != o.getClass()) return false;
281+
ContextArgument that = (ContextArgument) o;
282+
return a == that.a &&
283+
c == that.c &&
284+
Objects.equals(b, that.b);
285+
}
286+
287+
@Override
288+
public int hashCode() {
289+
return Objects.hash(a, b, c);
290+
}
291+
}
292+
293+
public void testContextAsArgument() throws IOException {
294+
InstantiatingObjectParser.Builder<ContextArgument, String> builder = InstantiatingObjectParser.builder(
295+
"foo",
296+
ContextArgument.class
297+
);
298+
builder.declareInt(constructorArg(), new ParseField("a"));
299+
builder.declareString(constructorArg(), new ParseField("b"));
300+
builder.declareString(constructorArg(), new ParseField("c"));
301+
InstantiatingObjectParser<ContextArgument, String> parser = builder.build();
302+
try (XContentParser contentParser = createParser(JsonXContent.jsonXContent, "{\"a\": 5, \"b\":\"6\", \"c\": \"7\"}")) {
303+
assertThat(parser.parse(contentParser, "context"), equalTo(new ContextArgument("context", 5, "6", 7)));
304+
}
305+
}
306+
307+
public void testContextAsArgumentWrongArgumentNumber() {
308+
InstantiatingObjectParser.Builder<ContextArgument, String> builder = InstantiatingObjectParser.builder(
309+
"foo",
310+
ContextArgument.class
311+
);
312+
builder.declareInt(constructorArg(), new ParseField("a"));
313+
builder.declareString(constructorArg(), new ParseField("b"));
314+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, builder::build);
315+
assertThat(e.getMessage(), containsString("Annotated constructor doesn't have 2 or 3 arguments in the class"));
316+
}
317+
243318
}

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java

+76-33
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010

1111
import org.elasticsearch.Version;
1212
import org.elasticsearch.index.mapper.TimeSeriesParams;
13+
import org.elasticsearch.xcontent.InstantiatingObjectParser;
1314
import org.elasticsearch.xcontent.ParseField;
1415
import org.elasticsearch.common.Strings;
1516
import org.elasticsearch.common.io.stream.StreamInput;
1617
import org.elasticsearch.common.io.stream.StreamOutput;
1718
import org.elasticsearch.common.io.stream.Writeable;
1819
import org.elasticsearch.xcontent.ConstructingObjectParser;
20+
import org.elasticsearch.xcontent.ParserConstructor;
1921
import org.elasticsearch.xcontent.ToXContentObject;
2022
import org.elasticsearch.xcontent.XContentBuilder;
2123
import org.elasticsearch.xcontent.XContentParser;
@@ -159,6 +161,59 @@ public FieldCapabilities(String name, String type,
159161

160162
}
161163

164+
/**
165+
* Constructor for a set of indices used by parser
166+
* @param name The name of the field
167+
* @param type The type associated with the field.
168+
* @param isMetadataField Whether this field is a metadata field.
169+
* @param isSearchable Whether this field is indexed for search.
170+
* @param isAggregatable Whether this field can be aggregated on.
171+
* @param isDimension Whether this field can be used as dimension
172+
* @param metricType If this field is a metric field, returns the metric's type or null for non-metrics fields
173+
* @param indices The list of indices where this field name is defined as {@code type},
174+
* or null if all indices have the same {@code type} for the field.
175+
* @param nonSearchableIndices The list of indices where this field is not searchable,
176+
* or null if the field is searchable in all indices.
177+
* @param nonAggregatableIndices The list of indices where this field is not aggregatable,
178+
* or null if the field is aggregatable in all indices.
179+
* @param nonDimensionIndices The list of indices where this field is not a dimension
180+
* @param metricConflictsIndices The list of indices where this field is has different metric types or not mark as a metric
181+
* @param meta Merged metadata across indices.
182+
*/
183+
@SuppressWarnings("unused")
184+
@ParserConstructor
185+
public FieldCapabilities(
186+
String name,
187+
String type,
188+
Boolean isMetadataField,
189+
boolean isSearchable,
190+
boolean isAggregatable,
191+
Boolean isDimension,
192+
String metricType,
193+
List<String> indices,
194+
List<String> nonSearchableIndices,
195+
List<String> nonAggregatableIndices,
196+
List<String> nonDimensionIndices,
197+
List<String> metricConflictsIndices,
198+
Map<String, Set<String>> meta
199+
) {
200+
this(
201+
name,
202+
type,
203+
isMetadataField == null ? false : isMetadataField,
204+
isSearchable,
205+
isAggregatable,
206+
isDimension == null ? false : isDimension,
207+
metricType != null ? Enum.valueOf(TimeSeriesParams.MetricType.class, metricType) : null,
208+
indices != null ? indices.toArray(new String[0]) : null,
209+
nonSearchableIndices != null ? nonSearchableIndices.toArray(new String[0]) : null,
210+
nonAggregatableIndices != null ? nonAggregatableIndices.toArray(new String[0]) : null,
211+
nonDimensionIndices != null ? nonDimensionIndices.toArray(new String[0]) : null,
212+
metricConflictsIndices != null ? metricConflictsIndices.toArray(new String[0]) : null,
213+
meta != null ? meta : Collections.emptyMap()
214+
);
215+
}
216+
162217
FieldCapabilities(StreamInput in) throws IOException {
163218
this.name = in.readString();
164219
this.type = in.readString();
@@ -254,43 +309,31 @@ public static FieldCapabilities fromXContent(String name, XContentParser parser)
254309
}
255310

256311
@SuppressWarnings("unchecked")
257-
private static final ConstructingObjectParser<FieldCapabilities, String> PARSER = new ConstructingObjectParser<>(
258-
"field_capabilities",
259-
true,
260-
(a, name) -> new FieldCapabilities(
261-
name,
262-
(String) a[0],
263-
a[3] == null ? false : (boolean) a[3],
264-
(boolean) a[1],
265-
(boolean) a[2],
266-
a[4] == null ? false : (boolean) a[4],
267-
a[5] != null ? Enum.valueOf(TimeSeriesParams.MetricType.class, (String) a[5]) : null,
268-
a[6] != null ? ((List<String>) a[6]).toArray(new String[0]) : null,
269-
a[7] != null ? ((List<String>) a[7]).toArray(new String[0]) : null,
270-
a[8] != null ? ((List<String>) a[8]).toArray(new String[0]) : null,
271-
a[9] != null ? ((List<String>) a[9]).toArray(new String[0]) : null,
272-
a[10] != null ? ((List<String>) a[10]).toArray(new String[0]) : null,
273-
a[11] != null ? ((Map<String, Set<String>>) a[11]) : Collections.emptyMap()
274-
)
275-
);
312+
private static final InstantiatingObjectParser<FieldCapabilities, String> PARSER;
276313

277314
static {
278-
PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD); // 0
279-
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), SEARCHABLE_FIELD); // 1
280-
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), AGGREGATABLE_FIELD); // 2
281-
PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), IS_METADATA_FIELD); // 3
282-
PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), TIME_SERIES_DIMENSION_FIELD); // 4
283-
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), TIME_SERIES_METRIC_FIELD); // 5
284-
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), INDICES_FIELD); // 6
285-
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_SEARCHABLE_INDICES_FIELD); // 7
286-
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_AGGREGATABLE_INDICES_FIELD); // 8
287-
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_DIMENSION_INDICES_FIELD); // 9
288-
PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), METRIC_CONFLICTS_INDICES_FIELD); // 10
289-
PARSER.declareObject(
315+
InstantiatingObjectParser.Builder<FieldCapabilities, String> parser = InstantiatingObjectParser.builder(
316+
"field_capabilities",
317+
true,
318+
FieldCapabilities.class
319+
);
320+
parser.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD);
321+
parser.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), IS_METADATA_FIELD);
322+
parser.declareBoolean(ConstructingObjectParser.constructorArg(), SEARCHABLE_FIELD);
323+
parser.declareBoolean(ConstructingObjectParser.constructorArg(), AGGREGATABLE_FIELD);
324+
parser.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), TIME_SERIES_DIMENSION_FIELD);
325+
parser.declareString(ConstructingObjectParser.optionalConstructorArg(), TIME_SERIES_METRIC_FIELD);
326+
parser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), INDICES_FIELD);
327+
parser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_SEARCHABLE_INDICES_FIELD);
328+
parser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_AGGREGATABLE_INDICES_FIELD);
329+
parser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), NON_DIMENSION_INDICES_FIELD);
330+
parser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), METRIC_CONFLICTS_INDICES_FIELD);
331+
parser.declareObject(
290332
ConstructingObjectParser.optionalConstructorArg(),
291-
(parser, context) -> parser.map(HashMap::new, p -> Set.copyOf(p.list())),
333+
(p, context) -> p.map(HashMap::new, v -> Set.copyOf(v.list())),
292334
META_FIELD
293-
); // 11
335+
);
336+
PARSER = parser.build();
294337
}
295338

296339
/**

0 commit comments

Comments
 (0)