Skip to content

Commit 001fef1

Browse files
authored
Refactor json parsing in immutable handlers (#88492)
1 parent 0a94091 commit 001fef1

File tree

8 files changed

+114
-91
lines changed

8 files changed

+114
-91
lines changed

server/src/main/java/org/elasticsearch/common/util/Maps.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -284,20 +284,6 @@ static int capacity(int expectedSize) {
284284
return expectedSize < 2 ? expectedSize + 1 : (int) (expectedSize / 0.75 + 1.0);
285285
}
286286

287-
/**
288-
* Convenience method to convert the passed in input object as a map with String keys.
289-
*
290-
* @param input the input passed into the operator handler after parsing the content
291-
* @return
292-
*/
293-
@SuppressWarnings("unchecked")
294-
public static Map<String, ?> asMap(Object input) {
295-
if (input instanceof Map<?, ?> source) {
296-
return (Map<String, Object>) source;
297-
}
298-
throw new IllegalStateException("Unsupported input format");
299-
}
300-
301287
/**
302288
* This method creates a copy of the {@code source} map using {@code copyValueFunction} to create a defensive copy of each value.
303289
*/

server/src/main/java/org/elasticsearch/immutablestate/ImmutableClusterStateHandler.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import org.elasticsearch.action.ActionRequestValidationException;
1212
import org.elasticsearch.action.support.master.MasterNodeRequest;
13+
import org.elasticsearch.xcontent.XContentParser;
1314

15+
import java.io.IOException;
1416
import java.util.Collection;
1517
import java.util.Collections;
1618

@@ -35,7 +37,6 @@ public interface ImmutableClusterStateHandler<T> {
3537
* cluster state update and the cluster state is typically supplied as a combined content,
3638
* unlike the REST handlers. This name must match a desired content key name in the combined
3739
* cluster state update, e.g. "ilm" or "cluster_settings" (for persistent cluster settings update).
38-
* </p>
3940
*
4041
* @return a String with the handler name, e.g "ilm".
4142
*/
@@ -51,7 +52,6 @@ public interface ImmutableClusterStateHandler<T> {
5152
* state in one go. For that reason, we supply a wrapper class to the cluster state called
5253
* {@link TransformState}, which contains the current cluster state as well as any previous keys
5354
* set by this handler on prior invocation.
54-
* </p>
5555
*
5656
* @param source The parsed information specific to this handler from the combined cluster state content
5757
* @param prevState The previous cluster state and keys set by this handler (if any)
@@ -70,7 +70,6 @@ public interface ImmutableClusterStateHandler<T> {
7070
* to any immutable handler to declare other immutable state handlers it depends on. Given dependencies exist,
7171
* the ImmutableClusterStateController will order those handlers such that the handlers that are dependent
7272
* on are processed first.
73-
* </p>
7473
*
7574
* @return a collection of immutable state handler names
7675
*/
@@ -85,7 +84,6 @@ default Collection<String> dependencies() {
8584
* All implementations of {@link ImmutableClusterStateHandler} should call the request validate method, by calling this default
8685
* implementation. To aid in any special validation logic that may need to be implemented by the immutable cluster state handler
8786
* we provide this convenience method.
88-
* </p>
8987
*
9088
* @param request the master node request that we base this immutable state handler on
9189
*/
@@ -95,4 +93,18 @@ default void validate(MasterNodeRequest<?> request) {
9593
throw new IllegalStateException("Validation error", exception);
9694
}
9795
}
96+
97+
/**
98+
* The parse content method which is called during parsing of file based content.
99+
*
100+
* <p>
101+
* The immutable state can be provided as XContent, which means that each handler needs
102+
* to implement a method to convert an XContent to an object it can consume later in
103+
* transform
104+
*
105+
* @param parser the XContent parser we are parsing from
106+
* @return
107+
* @throws IOException
108+
*/
109+
T fromXContent(XContentParser parser) throws IOException;
98110
}

server/src/main/java/org/elasticsearch/immutablestate/action/ImmutableClusterSettingsAction.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,22 @@
1515
import org.elasticsearch.common.settings.ClusterSettings;
1616
import org.elasticsearch.immutablestate.ImmutableClusterStateHandler;
1717
import org.elasticsearch.immutablestate.TransformState;
18+
import org.elasticsearch.xcontent.XContentParser;
1819

20+
import java.io.IOException;
1921
import java.util.HashMap;
2022
import java.util.HashSet;
2123
import java.util.Map;
2224
import java.util.Set;
2325
import java.util.stream.Collectors;
2426

25-
import static org.elasticsearch.common.util.Maps.asMap;
26-
2727
/**
2828
* This Action is the immutable state save version of RestClusterUpdateSettingsAction
2929
* <p>
3030
* It is used by the ImmutableClusterStateController to update the persistent cluster settings.
3131
* Since transient cluster settings are deprecated, this action doesn't support updating transient cluster settings.
3232
*/
33-
public class ImmutableClusterSettingsAction implements ImmutableClusterStateHandler<ClusterUpdateSettingsRequest> {
33+
public class ImmutableClusterSettingsAction implements ImmutableClusterStateHandler<Map<String, Object>> {
3434

3535
public static final String NAME = "cluster_settings";
3636

@@ -49,11 +49,12 @@ public String name() {
4949
private ClusterUpdateSettingsRequest prepare(Object input, Set<String> previouslySet) {
5050
final ClusterUpdateSettingsRequest clusterUpdateSettingsRequest = Requests.clusterUpdateSettingsRequest();
5151

52-
Map<String, ?> source = asMap(input);
5352
Map<String, Object> persistentSettings = new HashMap<>();
5453
Set<String> toDelete = new HashSet<>(previouslySet);
5554

56-
source.forEach((k, v) -> {
55+
Map<String, Object> settings = (Map<String, Object>) input;
56+
57+
settings.forEach((k, v) -> {
5758
persistentSettings.put(k, v);
5859
toDelete.remove(k);
5960
});
@@ -87,4 +88,9 @@ public TransformState transform(Object input, TransformState prevState) {
8788

8889
return new TransformState(state, currentKeys);
8990
}
91+
92+
@Override
93+
public Map<String, Object> fromXContent(XContentParser parser) throws IOException {
94+
return parser.map();
95+
}
9096
}

server/src/test/java/org/elasticsearch/action/admin/cluster/settings/ClusterUpdateSettingsRequestTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88

99
package org.elasticsearch.action.admin.cluster.settings;
1010

11+
import org.elasticsearch.action.support.ActionFilters;
12+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
13+
import org.elasticsearch.cluster.service.ClusterService;
1114
import org.elasticsearch.common.bytes.BytesReference;
15+
import org.elasticsearch.common.settings.ClusterSettings;
16+
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.immutablestate.action.ImmutableClusterSettingsAction;
1218
import org.elasticsearch.test.ESTestCase;
1319
import org.elasticsearch.test.XContentTestUtils;
20+
import org.elasticsearch.threadpool.ThreadPool;
21+
import org.elasticsearch.transport.TransportService;
1422
import org.elasticsearch.xcontent.ToXContent;
1523
import org.elasticsearch.xcontent.XContentParseException;
1624
import org.elasticsearch.xcontent.XContentParser;
@@ -21,6 +29,8 @@
2129

2230
import static org.hamcrest.CoreMatchers.containsString;
2331
import static org.hamcrest.CoreMatchers.equalTo;
32+
import static org.hamcrest.Matchers.containsInAnyOrder;
33+
import static org.mockito.Mockito.mock;
2434

2535
public class ClusterUpdateSettingsRequestTests extends ESTestCase {
2636

@@ -71,4 +81,43 @@ private static ClusterUpdateSettingsRequest createTestItem() {
7181
request.transientSettings(ClusterUpdateSettingsResponseTests.randomClusterSettings(0, 2));
7282
return request;
7383
}
84+
85+
public void testOperatorHandler() throws IOException {
86+
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
87+
88+
TransportClusterUpdateSettingsAction action = new TransportClusterUpdateSettingsAction(
89+
mock(TransportService.class),
90+
mock(ClusterService.class),
91+
mock(ThreadPool.class),
92+
mock(ActionFilters.class),
93+
mock(IndexNameExpressionResolver.class),
94+
clusterSettings
95+
);
96+
97+
assertEquals(ImmutableClusterSettingsAction.NAME, action.immutableStateHandlerName().get());
98+
99+
String oneSettingJSON = """
100+
{
101+
"persistent": {
102+
"indices.recovery.max_bytes_per_sec": "25mb",
103+
"cluster": {
104+
"remote": {
105+
"cluster_one": {
106+
"seeds": [
107+
"127.0.0.1:9300"
108+
]
109+
}
110+
}
111+
}
112+
}
113+
}""";
114+
115+
try (XContentParser parser = createParser(XContentType.JSON.xContent(), oneSettingJSON)) {
116+
ClusterUpdateSettingsRequest parsedRequest = ClusterUpdateSettingsRequest.fromXContent(parser);
117+
assertThat(
118+
action.modifiedKeys(parsedRequest),
119+
containsInAnyOrder("indices.recovery.max_bytes_per_sec", "cluster.remote.cluster_one.seeds")
120+
);
121+
}
122+
}
74123
}

server/src/test/java/org/elasticsearch/immutablestate/ImmutableClusterStateHandlerTests.java

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,11 @@
1010

1111
import org.elasticsearch.action.ActionRequestValidationException;
1212
import org.elasticsearch.action.support.master.MasterNodeRequest;
13-
import org.elasticsearch.common.util.Maps;
14-
import org.elasticsearch.common.xcontent.XContentHelper;
1513
import org.elasticsearch.indices.settings.InternalOrPrivateSettingsPlugin;
1614
import org.elasticsearch.test.ESTestCase;
1715
import org.elasticsearch.xcontent.XContentParser;
18-
import org.elasticsearch.xcontent.XContentParserConfiguration;
19-
import org.elasticsearch.xcontent.XContentType;
2016

2117
import java.io.IOException;
22-
import java.util.Map;
23-
24-
import static org.hamcrest.Matchers.containsInAnyOrder;
2518

2619
public class ImmutableClusterStateHandlerTests extends ESTestCase {
2720
public void testValidation() {
@@ -35,6 +28,11 @@ public String name() {
3528
public TransformState transform(Object source, TransformState prevState) throws Exception {
3629
return prevState;
3730
}
31+
32+
@Override
33+
public ValidRequest fromXContent(XContentParser parser) throws IOException {
34+
return new ValidRequest();
35+
}
3836
};
3937

4038
handler.validate(new ValidRequest());
@@ -44,53 +42,6 @@ public TransformState transform(Object source, TransformState prevState) throws
4442
);
4543
}
4644

47-
public void testAsMapAndFromMap() throws IOException {
48-
String someJSON = """
49-
{
50-
"persistent": {
51-
"indices.recovery.max_bytes_per_sec": "25mb",
52-
"cluster": {
53-
"remote": {
54-
"cluster_one": {
55-
"seeds": [
56-
"127.0.0.1:9300"
57-
]
58-
}
59-
}
60-
}
61-
}
62-
}""";
63-
64-
ImmutableClusterStateHandler<ValidRequest> persistentHandler = new ImmutableClusterStateHandler<>() {
65-
@Override
66-
public String name() {
67-
return "persistent";
68-
}
69-
70-
@Override
71-
public TransformState transform(Object source, TransformState prevState) throws Exception {
72-
return prevState;
73-
}
74-
};
75-
76-
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, someJSON)) {
77-
Map<String, Object> originalMap = parser.map();
78-
79-
Map<String, ?> internalHandlerMap = Maps.asMap(originalMap.get(persistentHandler.name()));
80-
assertThat(internalHandlerMap.keySet(), containsInAnyOrder("indices.recovery.max_bytes_per_sec", "cluster"));
81-
assertEquals(
82-
"Unsupported input format",
83-
expectThrows(IllegalStateException.class, () -> Maps.asMap(Integer.valueOf(123))).getMessage()
84-
);
85-
86-
try (XContentParser newParser = XContentHelper.mapToXContentParser(XContentParserConfiguration.EMPTY, originalMap)) {
87-
Map<String, Object> newMap = newParser.map();
88-
89-
assertThat(newMap.keySet(), containsInAnyOrder("persistent"));
90-
}
91-
}
92-
}
93-
9445
static class ValidRequest extends MasterNodeRequest<InternalOrPrivateSettingsPlugin.UpdateInternalOrPrivateAction.Request> {
9546
@Override
9647
public ActionRequestValidationException validate() {

x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/ImmutableLifecycleAction.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.Set;
2929
import java.util.stream.Collectors;
3030

31-
import static org.elasticsearch.common.util.Maps.asMap;
3231
import static org.elasticsearch.common.xcontent.XContentHelper.mapToXContentParser;
3332

3433
/**
@@ -38,7 +37,7 @@
3837
* Internally it uses {@link TransportPutLifecycleAction} and
3938
* {@link TransportDeleteLifecycleAction} to add, update and delete ILM policies.
4039
*/
41-
public class ImmutableLifecycleAction implements ImmutableClusterStateHandler<LifecyclePolicy> {
40+
public class ImmutableLifecycleAction implements ImmutableClusterStateHandler<List<LifecyclePolicy>> {
4241

4342
private final NamedXContentRegistry xContentRegistry;
4443
private final Client client;
@@ -60,18 +59,12 @@ public String name() {
6059
@SuppressWarnings("unchecked")
6160
public Collection<PutLifecycleAction.Request> prepare(Object input) throws IOException {
6261
List<PutLifecycleAction.Request> result = new ArrayList<>();
62+
List<LifecyclePolicy> policies = (List<LifecyclePolicy>) input;
6363

64-
Map<String, ?> source = asMap(input);
65-
66-
for (String name : source.keySet()) {
67-
Map<String, ?> content = (Map<String, ?>) source.get(name);
68-
var config = XContentParserConfiguration.EMPTY.withRegistry(LifecyclePolicyConfig.DEFAULT_X_CONTENT_REGISTRY);
69-
try (XContentParser parser = mapToXContentParser(config, content)) {
70-
LifecyclePolicy policy = LifecyclePolicy.parse(parser, name);
71-
PutLifecycleAction.Request request = new PutLifecycleAction.Request(policy);
72-
validate(request);
73-
result.add(request);
74-
}
64+
for (var policy : policies) {
65+
PutLifecycleAction.Request request = new PutLifecycleAction.Request(policy);
66+
validate(request);
67+
result.add(request);
7568
}
7669

7770
return result;
@@ -108,4 +101,22 @@ public TransformState transform(Object source, TransformState prevState) throws
108101

109102
return new TransformState(state, entities);
110103
}
104+
105+
@Override
106+
public List<LifecyclePolicy> fromXContent(XContentParser parser) throws IOException {
107+
List<LifecyclePolicy> result = new ArrayList<>();
108+
109+
Map<String, ?> source = parser.map();
110+
var config = XContentParserConfiguration.EMPTY.withRegistry(LifecyclePolicyConfig.DEFAULT_X_CONTENT_REGISTRY);
111+
112+
for (String name : source.keySet()) {
113+
@SuppressWarnings("unchecked")
114+
Map<String, ?> content = (Map<String, ?>) source.get(name);
115+
try (XContentParser policyParser = mapToXContentParser(config, content)) {
116+
result.add(LifecyclePolicy.parse(policyParser, name));
117+
}
118+
}
119+
120+
return result;
121+
}
111122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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; you may not use this file except in compliance with the Elastic License
5+
# 2.0.
6+
#
7+
8+
org.elasticsearch.xpack.ilm.ILMImmutableStateHandlerProvider

x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/action/ImmutableILMStateControllerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected NamedXContentRegistry xContentRegistry() {
8787

8888
private TransformState processJSON(ImmutableLifecycleAction action, TransformState prevState, String json) throws Exception {
8989
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, json)) {
90-
return action.transform(parser.map(), prevState);
90+
return action.transform(action.fromXContent(parser), prevState);
9191
}
9292
}
9393

0 commit comments

Comments
 (0)