Skip to content

Commit a8258bd

Browse files
authored
Collect config for XContentParser (#79814)
This shifts the internal API for building an `XContentParser` to make maintenance easier. To build an `XContentParser` you need three things: 1. An `XContentType` that knows whether you are reading json, smile, yaml, or cbor 2. Configuration information 3. A place to get the bytes Years ago, there wasn't any configuration. But we've since added stuff like `NamedXContentRegistry` and `DeprecationHandler` and `RestApiVersion` and, more lately, filtering configuration. Each time we added one we modified the method signature to add it. Each "place to get the bytes" also needs its own method. All of which are implemented by each `XContentType`. All together we had dozens of these methods for different ways of building parser. All because of the combinatorial explosion of `config x type x source`. This tames the combinatorial explosion somewhat by bundling all of the configuration into an immutable `XContentParserConfiguration`. Sadly, it has to add `createParser` methods that consume the configuration objects. Luckily, we can remove some of the old methods. We *could* remove all of them, but that'd make this change *huge*. Instead we deprecate the old methods and point them to the new ones.
1 parent 6b20e8e commit a8258bd

File tree

30 files changed

+406
-842
lines changed

30 files changed

+406
-842
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/search/fetch/subphase/FetchSourcePhaseBenchmark.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import org.elasticsearch.xcontent.NamedXContentRegistry;
1313
import org.elasticsearch.xcontent.XContentBuilder;
1414
import org.elasticsearch.xcontent.XContentParser;
15+
import org.elasticsearch.xcontent.XContentParserConfiguration;
1516
import org.elasticsearch.xcontent.XContentType;
16-
import org.elasticsearch.xcontent.support.filtering.FilterPath;
1717
import org.openjdk.jmh.annotations.Benchmark;
1818
import org.openjdk.jmh.annotations.BenchmarkMode;
1919
import org.openjdk.jmh.annotations.Fork;
@@ -41,8 +41,7 @@ public class FetchSourcePhaseBenchmark {
4141
private FetchSourceContext fetchContext;
4242
private Set<String> includesSet;
4343
private Set<String> excludesSet;
44-
private FilterPath[] includesFilters;
45-
private FilterPath[] excludesFilters;
44+
private XContentParserConfiguration parserConfig;
4645

4746
@Param({ "tiny", "short", "one_4k_field", "one_4m_field" })
4847
private String source;
@@ -76,8 +75,7 @@ public void setup() throws IOException {
7675
);
7776
includesSet = Set.of(fetchContext.includes());
7877
excludesSet = Set.of(fetchContext.excludes());
79-
includesFilters = FilterPath.compile(Set.of(fetchContext.includes()));
80-
excludesFilters = FilterPath.compile(Set.of(fetchContext.excludes()));
78+
parserConfig = XContentParserConfiguration.EMPTY.withFiltering(includesSet, excludesSet);
8179
}
8280

8381
private BytesReference read300BytesExample() throws IOException {
@@ -102,16 +100,7 @@ public BytesReference filterObjects() throws IOException {
102100
public BytesReference filterXContentOnParser() throws IOException {
103101
BytesStreamOutput streamOutput = new BytesStreamOutput(Math.min(1024, sourceBytes.length()));
104102
XContentBuilder builder = new XContentBuilder(XContentType.JSON.xContent(), streamOutput);
105-
try (
106-
XContentParser parser = XContentType.JSON.xContent()
107-
.createParser(
108-
NamedXContentRegistry.EMPTY,
109-
DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
110-
sourceBytes.streamInput(),
111-
includesFilters,
112-
excludesFilters
113-
)
114-
) {
103+
try (XContentParser parser = XContentType.JSON.xContent().createParser(parserConfig, sourceBytes.streamInput())) {
115104
builder.copyCurrentStructure(parser);
116105
return BytesReference.bytes(builder);
117106
}

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
import org.elasticsearch.common.io.Streams;
5656
import org.elasticsearch.common.lucene.uid.Versions;
5757
import org.elasticsearch.common.xcontent.XContentHelper;
58-
import org.elasticsearch.core.RestApiVersion;
5958
import org.elasticsearch.core.TimeValue;
6059
import org.elasticsearch.core.Tuple;
6160
import org.elasticsearch.index.VersionType;
@@ -1257,17 +1256,16 @@ public void testMultiSearch() throws IOException {
12571256
requests.add(searchRequest);
12581257
};
12591258
MultiSearchRequest.readMultiLineFormat(
1260-
new BytesArray(EntityUtils.toByteArray(request.getEntity())),
12611259
REQUEST_BODY_CONTENT_TYPE.xContent(),
1260+
parserConfig(),
1261+
new BytesArray(EntityUtils.toByteArray(request.getEntity())),
12621262
consumer,
12631263
null,
12641264
multiSearchRequest.indicesOptions(),
12651265
null,
12661266
null,
12671267
null,
1268-
xContentRegistry(),
1269-
true,
1270-
RestApiVersion.current()
1268+
true
12711269
);
12721270
assertEquals(requests, multiSearchRequest.requests());
12731271
}

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

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88

99
package org.elasticsearch.xcontent;
1010

11-
import org.elasticsearch.core.RestApiVersion;
12-
import org.elasticsearch.xcontent.support.filtering.FilterPath;
13-
1411
import java.io.IOException;
1512
import java.io.InputStream;
1613
import java.io.OutputStream;
@@ -50,78 +47,67 @@ default XContentGenerator createGenerator(OutputStream os) throws IOException {
5047
/**
5148
* Creates a parser over the provided string content.
5249
*/
53-
XContentParser createParser(NamedXContentRegistry xContentRegistry, DeprecationHandler deprecationHandler, String content)
54-
throws IOException;
50+
XContentParser createParser(XContentParserConfiguration config, String content) throws IOException;
51+
52+
/**
53+
* Creates a parser over the provided string content.
54+
* @deprecated Use {@link #createParser(XContentParserConfiguration, InputStream)}
55+
*/
56+
@Deprecated
57+
default XContentParser createParser(NamedXContentRegistry registry, DeprecationHandler deprecationHandler, String content)
58+
throws IOException {
59+
return createParser(XContentParserConfiguration.EMPTY.withRegistry(registry).withDeprecationHandler(deprecationHandler), content);
60+
}
5561

5662
/**
5763
* Creates a parser over the provided input stream.
5864
*/
59-
XContentParser createParser(NamedXContentRegistry xContentRegistry, DeprecationHandler deprecationHandler, InputStream is)
60-
throws IOException;
65+
XContentParser createParser(XContentParserConfiguration config, InputStream is) throws IOException;
6166

6267
/**
6368
* Creates a parser over the provided input stream.
69+
* @deprecated Use {@link #createParser(XContentParserConfiguration, InputStream)}
6470
*/
65-
XContentParser createParser(
66-
NamedXContentRegistry xContentRegistry,
67-
DeprecationHandler deprecationHandler,
68-
InputStream is,
69-
FilterPath[] includes,
70-
FilterPath[] excludes
71-
) throws IOException;
71+
@Deprecated
72+
default XContentParser createParser(NamedXContentRegistry registry, DeprecationHandler deprecationHandler, InputStream is)
73+
throws IOException {
74+
return createParser(XContentParserConfiguration.EMPTY.withRegistry(registry).withDeprecationHandler(deprecationHandler), is);
75+
}
7276

7377
/**
7478
* Creates a parser over the provided bytes.
7579
*/
76-
XContentParser createParser(NamedXContentRegistry xContentRegistry, DeprecationHandler deprecationHandler, byte[] data)
77-
throws IOException;
80+
default XContentParser createParser(XContentParserConfiguration config, byte[] data) throws IOException {
81+
return createParser(config, data, 0, data.length);
82+
}
7883

7984
/**
8085
* Creates a parser over the provided bytes.
86+
* @deprecated Use {@link #createParser(XContentParserConfiguration, byte[])}
8187
*/
82-
XContentParser createParser(
83-
NamedXContentRegistry xContentRegistry,
84-
DeprecationHandler deprecationHandler,
85-
byte[] data,
86-
int offset,
87-
int length
88-
) throws IOException;
88+
@Deprecated
89+
default XContentParser createParser(NamedXContentRegistry registry, DeprecationHandler deprecationHandler, byte[] data)
90+
throws IOException {
91+
return createParser(XContentParserConfiguration.EMPTY.withRegistry(registry).withDeprecationHandler(deprecationHandler), data);
92+
}
8993

90-
XContentParser createParser(
91-
NamedXContentRegistry xContentRegistry,
92-
DeprecationHandler deprecationHandler,
93-
byte[] data,
94-
int offset,
95-
int length,
96-
FilterPath[] includes,
97-
FilterPath[] excludes
98-
) throws IOException;
94+
/**
95+
* Creates a parser over the provided bytes.
96+
*/
97+
XContentParser createParser(XContentParserConfiguration config, byte[] data, int offset, int length) throws IOException;
9998

10099
/**
101100
* Creates a parser over the provided reader.
102101
*/
103-
XContentParser createParser(NamedXContentRegistry xContentRegistry, DeprecationHandler deprecationHandler, Reader reader)
104-
throws IOException;
102+
XContentParser createParser(XContentParserConfiguration config, Reader reader) throws IOException;
105103

106104
/**
107-
* Creates a parser over the provided input stream and with the indication that a request is using REST compatible API.
108-
*
109-
* @param restApiVersion - indicates if the N-1 or N compatible XContent parsing logic will be used.
105+
* Creates a parser over the provided reader.
106+
* @deprecated Use {@link #createParser(XContentParserConfiguration, Reader)}
110107
*/
111-
XContentParser createParserForCompatibility(
112-
NamedXContentRegistry xContentRegistry,
113-
DeprecationHandler deprecationHandler,
114-
InputStream is,
115-
RestApiVersion restApiVersion
116-
) throws IOException;
117-
118-
XContentParser createParserForCompatibility(
119-
NamedXContentRegistry xContentRegistry,
120-
DeprecationHandler deprecationHandler,
121-
byte[] data,
122-
int offset,
123-
int length,
124-
RestApiVersion restApiVersion
125-
) throws IOException;
126-
108+
@Deprecated
109+
default XContentParser createParser(NamedXContentRegistry registry, DeprecationHandler deprecationHandler, Reader reader)
110+
throws IOException {
111+
return createParser(XContentParserConfiguration.EMPTY.withRegistry(registry).withDeprecationHandler(deprecationHandler), reader);
112+
}
127113
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.xcontent;
10+
11+
import com.fasterxml.jackson.core.JsonParser;
12+
import com.fasterxml.jackson.core.filter.FilteringParserDelegate;
13+
14+
import org.elasticsearch.core.RestApiVersion;
15+
import org.elasticsearch.xcontent.support.filtering.FilterPath;
16+
import org.elasticsearch.xcontent.support.filtering.FilterPathBasedFilter;
17+
18+
import java.util.Set;
19+
20+
/**
21+
* Configuration for {@link XContentParser}.
22+
*/
23+
public class XContentParserConfiguration {
24+
/**
25+
* Creates parsers that don't support {@link XContentParser#namedObject},
26+
* throw an exception when they see deprecated fields, that return the
27+
* {@link RestApiVersion#current() current version} from
28+
* {@link XContentParser#getRestApiVersion}, and do no filtering.
29+
*/
30+
public static final XContentParserConfiguration EMPTY = new XContentParserConfiguration(
31+
NamedXContentRegistry.EMPTY,
32+
DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
33+
RestApiVersion.current(),
34+
null,
35+
null
36+
);
37+
38+
final NamedXContentRegistry registry;
39+
final DeprecationHandler deprecationHandler;
40+
final RestApiVersion restApiVersion;
41+
final FilterPath[] includes;
42+
final FilterPath[] excludes;
43+
44+
private XContentParserConfiguration(
45+
NamedXContentRegistry registry,
46+
DeprecationHandler deprecationHandler,
47+
RestApiVersion restApiVersion,
48+
FilterPath[] includes,
49+
FilterPath[] excludes
50+
) {
51+
this.registry = registry;
52+
this.deprecationHandler = deprecationHandler;
53+
this.restApiVersion = restApiVersion;
54+
this.includes = includes;
55+
this.excludes = excludes;
56+
}
57+
58+
/**
59+
* Replace the registry backing {@link XContentParser#namedObject}.
60+
*/
61+
public XContentParserConfiguration withRegistry(NamedXContentRegistry registry) {
62+
return new XContentParserConfiguration(registry, deprecationHandler, restApiVersion, includes, excludes);
63+
}
64+
65+
public NamedXContentRegistry registry() {
66+
return registry;
67+
}
68+
69+
/**
70+
* Replace the behavior of {@link XContentParser} when it encounters
71+
* a deprecated field.
72+
*/
73+
public XContentParserConfiguration withDeprecationHandler(DeprecationHandler deprecationHandler) {
74+
return new XContentParserConfiguration(registry, deprecationHandler, restApiVersion, includes, excludes);
75+
}
76+
77+
public DeprecationHandler deprecationHandler() {
78+
return deprecationHandler;
79+
}
80+
81+
/**
82+
* Replace the {@link XContentParser#getRestApiVersion() claimed}
83+
* {@link RestApiVersion}.
84+
*/
85+
public XContentParserConfiguration withRestApiVersion(RestApiVersion restApiVersion) {
86+
return new XContentParserConfiguration(registry, deprecationHandler, restApiVersion, includes, excludes);
87+
}
88+
89+
public RestApiVersion restApiVersion() {
90+
return restApiVersion;
91+
}
92+
93+
/**
94+
* Replace the configured filtering.
95+
*/
96+
public XContentParserConfiguration withFiltering(Set<String> includes, Set<String> excludes) {
97+
return new XContentParserConfiguration(
98+
registry,
99+
deprecationHandler,
100+
restApiVersion,
101+
FilterPath.compile(includes),
102+
FilterPath.compile(excludes)
103+
);
104+
}
105+
106+
public JsonParser filter(JsonParser parser) {
107+
JsonParser filtered = parser;
108+
if (excludes != null) {
109+
for (FilterPath e : excludes) {
110+
if (e.hasDoubleWildcard()) {
111+
// Fixed in Jackson 2.13 - https://github.com/FasterXML/jackson-core/issues/700
112+
throw new UnsupportedOperationException("double wildcards are not supported in filtered excludes");
113+
}
114+
}
115+
filtered = new FilteringParserDelegate(filtered, new FilterPathBasedFilter(excludes, false), true, true);
116+
}
117+
if (includes != null) {
118+
filtered = new FilteringParserDelegate(filtered, new FilterPathBasedFilter(includes, true), true, true);
119+
}
120+
return filtered;
121+
}
122+
}

0 commit comments

Comments
 (0)