Skip to content

Commit 3a4b90a

Browse files
committed
Fix NPE when values is omitted on percentile_ranks agg (#26046)
An array of values is required because there is no default (or reasonable way to set a default). But validation for values only happens if it is actually set. If the values param is omitted entirely than the agg builder will NPE.
1 parent 37e67c9 commit 3a4b90a

File tree

11 files changed

+245
-160
lines changed

11 files changed

+245
-160
lines changed

core/src/main/java/org/elasticsearch/common/xcontent/AbstractObjectParser.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public abstract <T> void declareField(BiConsumer<Value, T> consumer, ContextPars
4949
/**
5050
* Declares named objects in the style of aggregations. These are named
5151
* inside and object like this:
52-
*
52+
*
5353
* <pre>
5454
* <code>
5555
* {
@@ -62,7 +62,7 @@ public abstract <T> void declareField(BiConsumer<Value, T> consumer, ContextPars
6262
* }
6363
* </code>
6464
* </pre>
65-
*
65+
*
6666
* Unlike the other version of this method, "ordered" mode (arrays of
6767
* objects) is not supported.
6868
*
@@ -82,7 +82,7 @@ public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer
8282
/**
8383
* Declares named objects in the style of highlighting's field element.
8484
* These are usually named inside and object like this:
85-
*
85+
*
8686
* <pre>
8787
* <code>
8888
* {
@@ -96,9 +96,9 @@ public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer
9696
* }
9797
* </code>
9898
* </pre>
99-
*
99+
*
100100
* but, when order is important, some may be written this way:
101-
*
101+
*
102102
* <pre>
103103
* <code>
104104
* {
@@ -112,7 +112,7 @@ public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer
112112
* }
113113
* </code>
114114
* </pre>
115-
*
115+
*
116116
* This is because json doesn't enforce ordering. Elasticsearch reads it in
117117
* the order sent but tools that generate json are free to put object
118118
* members in an unordered Map, jumbling them. Thus, if you care about order
@@ -134,6 +134,8 @@ public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer
134134
public abstract <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedObjectParser<T, Context> namedObjectParser,
135135
Consumer<Value> orderedModeCallback, ParseField parseField);
136136

137+
public abstract String getName();
138+
137139
public <T> void declareField(BiConsumer<Value, T> consumer, CheckedFunction<XContentParser, T, IOException> parser,
138140
ParseField parseField, ValueType type) {
139141
if (parser == null) {

core/src/main/java/org/elasticsearch/common/xcontent/ConstructingObjectParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ public <T> void declareNamedObjects(BiConsumer<Value, List<T>> consumer, NamedOb
295295
}
296296
}
297297

298+
public String getName() {
299+
return objectParser.getName();
300+
}
301+
298302
private Consumer<Target> wrapOrderedModeCallBack(Consumer<Value> callback) {
299303
return (target) -> {
300304
if (target.targetObject != null) {

core/src/main/java/org/elasticsearch/search/aggregations/AggregationBuilders.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,15 @@ public static SignificantTermsAggregationBuilder significantTerms(String name) {
247247
return new SignificantTermsAggregationBuilder(name, null);
248248
}
249249

250-
250+
251251
/**
252252
* Create a new {@link SignificantTextAggregationBuilder} aggregation with the given name and text field name
253253
*/
254254
public static SignificantTextAggregationBuilder significantText(String name, String fieldName) {
255255
return new SignificantTextAggregationBuilder(name, fieldName);
256-
}
257-
258-
256+
}
257+
258+
259259
/**
260260
* Create a new {@link DateHistogramAggregationBuilder} aggregation with the given
261261
* name.
@@ -304,8 +304,8 @@ public static PercentilesAggregationBuilder percentiles(String name) {
304304
/**
305305
* Create a new {@link PercentileRanks} aggregation with the given name.
306306
*/
307-
public static PercentileRanksAggregationBuilder percentileRanks(String name) {
308-
return new PercentileRanksAggregationBuilder(name);
307+
public static PercentileRanksAggregationBuilder percentileRanks(String name, double[] values) {
308+
return new PercentileRanksAggregationBuilder(name, values);
309309
}
310310

311311
/**

core/src/main/java/org/elasticsearch/search/aggregations/metrics/percentiles/PercentileRanksAggregationBuilder.java

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.elasticsearch.common.ParseField;
2323
import org.elasticsearch.common.io.stream.StreamInput;
2424
import org.elasticsearch.common.io.stream.StreamOutput;
25+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
26+
import org.elasticsearch.common.xcontent.ContextParser;
2527
import org.elasticsearch.common.xcontent.ObjectParser;
2628
import org.elasticsearch.common.xcontent.XContentBuilder;
2729
import org.elasticsearch.common.xcontent.XContentParser;
@@ -42,7 +44,11 @@
4244

4345
import java.io.IOException;
4446
import java.util.Arrays;
47+
import java.util.List;
4548
import java.util.Objects;
49+
import java.util.function.BiConsumer;
50+
51+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
4652

4753
public class PercentileRanksAggregationBuilder extends LeafOnly<ValuesSource.Numeric, PercentileRanksAggregationBuilder> {
4854
public static final String NAME = PercentileRanks.TYPE_NAME;
@@ -53,7 +59,7 @@ private static class TDigestOptions {
5359
Double compression;
5460
}
5561

56-
private static final ObjectParser<TDigestOptions, Void> TDIGEST_OPTIONS_PARSER =
62+
private static final ObjectParser<TDigestOptions, String> TDIGEST_OPTIONS_PARSER =
5763
new ObjectParser<>(PercentilesMethod.TDIGEST.getParseField().getPreferredName(), TDigestOptions::new);
5864
static {
5965
TDIGEST_OPTIONS_PARSER.declareDouble((opts, compression) -> opts.compression = compression, new ParseField("compression"));
@@ -63,22 +69,22 @@ private static class HDROptions {
6369
Integer numberOfSigDigits;
6470
}
6571

66-
private static final ObjectParser<HDROptions, Void> HDR_OPTIONS_PARSER =
72+
private static final ObjectParser<HDROptions, String> HDR_OPTIONS_PARSER =
6773
new ObjectParser<>(PercentilesMethod.HDR.getParseField().getPreferredName(), HDROptions::new);
6874
static {
6975
HDR_OPTIONS_PARSER.declareInt((opts, numberOfSigDigits) -> opts.numberOfSigDigits = numberOfSigDigits,
7076
new ParseField("number_of_significant_value_digits"));
7177
}
7278

73-
private static final ObjectParser<PercentileRanksAggregationBuilder, Void> PARSER;
79+
// The builder requires two parameters for the constructor: aggregation name and values array. The
80+
// agg name is supplied externally via the Parser's context (as a String), while the values array
81+
// is parsed from the request and supplied to the ConstructingObjectParser as a ctor argument
82+
private static final ConstructingObjectParser<PercentileRanksAggregationBuilder, String> PARSER;
7483
static {
75-
PARSER = new ObjectParser<>(PercentileRanksAggregationBuilder.NAME);
84+
PARSER = new ConstructingObjectParser<>(PercentileRanksAggregationBuilder.NAME, false,
85+
(a, context) -> new PercentileRanksAggregationBuilder(context, (List) a[0]));
7686
ValuesSourceParserHelper.declareNumericFields(PARSER, true, false, false);
77-
78-
PARSER.declareDoubleArray(
79-
(b, v) -> b.values(v.stream().mapToDouble(Double::doubleValue).toArray()),
80-
VALUES_FIELD);
81-
87+
PARSER.declareDoubleArray(constructorArg(), VALUES_FIELD);
8288
PARSER.declareBoolean(PercentileRanksAggregationBuilder::keyed, PercentilesAggregationBuilder.KEYED_FIELD);
8389

8490
PARSER.declareField((b, v) -> {
@@ -97,7 +103,8 @@ private static class HDROptions {
97103
}
98104

99105
public static AggregationBuilder parse(String aggregationName, XContentParser parser) throws IOException {
100-
return PARSER.parse(parser, new PercentileRanksAggregationBuilder(aggregationName), null);
106+
// the aggregation name is supplied to the parser as a Context. See note at top of Parser for more details
107+
return PARSER.parse(parser, aggregationName);
101108
}
102109

103110
private double[] values;
@@ -106,8 +113,21 @@ public static AggregationBuilder parse(String aggregationName, XContentParser pa
106113
private double compression = 100.0;
107114
private boolean keyed = true;
108115

109-
public PercentileRanksAggregationBuilder(String name) {
116+
private PercentileRanksAggregationBuilder(String name, List<Double> values) {
117+
this(name, values.stream().mapToDouble(Double::doubleValue).toArray());
118+
}
119+
120+
public PercentileRanksAggregationBuilder(String name, double[] values) {
110121
super(name, ValuesSourceType.NUMERIC, ValueType.NUMERIC);
122+
if (values == null) {
123+
throw new IllegalArgumentException("[values] must not be null: [" + name + "]");
124+
}
125+
if (values.length == 0) {
126+
throw new IllegalArgumentException("[values] must not be an empty array: [" + name + "]");
127+
}
128+
double[] sortedValues = Arrays.copyOf(values, values.length);
129+
Arrays.sort(sortedValues);
130+
this.values = sortedValues;
111131
}
112132

113133
/**
@@ -131,19 +151,6 @@ protected void innerWriteTo(StreamOutput out) throws IOException {
131151
method.writeTo(out);
132152
}
133153

134-
/**
135-
* Set the values to compute percentiles from.
136-
*/
137-
public PercentileRanksAggregationBuilder values(double... values) {
138-
if (values == null) {
139-
throw new IllegalArgumentException("[values] must not be null: [" + name + "]");
140-
}
141-
double[] sortedValues = Arrays.copyOf(values, values.length);
142-
Arrays.sort(sortedValues);
143-
this.values = sortedValues;
144-
return this;
145-
}
146-
147154
/**
148155
* Get the values to compute percentiles from.
149156
*/

core/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceParserHelper.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.elasticsearch.common.ParseField;
2323
import org.elasticsearch.common.ParsingException;
24+
import org.elasticsearch.common.xcontent.AbstractObjectParser;
25+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
2426
import org.elasticsearch.common.xcontent.ObjectParser;
2527
import org.elasticsearch.common.xcontent.XContentParser;
2628
import org.elasticsearch.script.Script;
@@ -31,32 +33,32 @@ public final class ValuesSourceParserHelper {
3133

3234
private ValuesSourceParserHelper() {} // utility class, no instantiation
3335

34-
public static void declareAnyFields(
35-
ObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource, ?>, Void> objectParser,
36+
public static <T> void declareAnyFields(
37+
AbstractObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource, ?>, T> objectParser,
3638
boolean scriptable, boolean formattable) {
3739
declareFields(objectParser, scriptable, formattable, false, null);
3840
}
3941

40-
public static void declareNumericFields(
41-
ObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.Numeric, ?>, Void> objectParser,
42+
public static <T> void declareNumericFields(
43+
AbstractObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.Numeric, ?>, T> objectParser,
4244
boolean scriptable, boolean formattable, boolean timezoneAware) {
4345
declareFields(objectParser, scriptable, formattable, timezoneAware, ValueType.NUMERIC);
4446
}
4547

46-
public static void declareBytesFields(
47-
ObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.Bytes, ?>, Void> objectParser,
48+
public static <T> void declareBytesFields(
49+
AbstractObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.Bytes, ?>, T> objectParser,
4850
boolean scriptable, boolean formattable) {
4951
declareFields(objectParser, scriptable, formattable, false, ValueType.STRING);
5052
}
5153

52-
public static void declareGeoFields(
53-
ObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.GeoPoint, ?>, Void> objectParser,
54+
public static <T> void declareGeoFields(
55+
AbstractObjectParser<? extends ValuesSourceAggregationBuilder<ValuesSource.GeoPoint, ?>, T> objectParser,
5456
boolean scriptable, boolean formattable) {
5557
declareFields(objectParser, scriptable, formattable, false, ValueType.GEOPOINT);
5658
}
5759

58-
private static <VS extends ValuesSource> void declareFields(
59-
ObjectParser<? extends ValuesSourceAggregationBuilder<VS, ?>, Void> objectParser,
60+
private static <VS extends ValuesSource, T> void declareFields(
61+
AbstractObjectParser<? extends ValuesSourceAggregationBuilder<VS, ?>, T> objectParser,
6062
boolean scriptable, boolean formattable, boolean timezoneAware, ValueType targetValueType) {
6163

6264

@@ -99,4 +101,6 @@ private static <VS extends ValuesSource> void declareFields(
99101
}
100102
}
101103

104+
105+
102106
}

0 commit comments

Comments
 (0)