Skip to content

Commit 4a07802

Browse files
authored
Scripting: add available languages & contexts API (#49652)
Adds `GET /_script_language` to support Kibana dynamic scripting language selection. Response contains whether `inline` and/or `stored` scripts are enabled as determined by the `script.allowed_types` settings. For each scripting language registered, such as `painless`, `expression`, `mustache` or custom, available contexts for the language are included as determined by the `script.allowed_contexts` setting. Response format: ``` { "types_allowed": [ "inline", "stored" ], "language_contexts": [ { "language": "expression", "contexts": [ "aggregation_selector", "aggs" ... ] }, { "language": "painless", "contexts": [ "aggregation_selector", "aggs", "aggs_combine", ... ] } ... ] } ``` Fixes: #49463
1 parent 20febe6 commit 4a07802

File tree

21 files changed

+858
-37
lines changed

21 files changed

+858
-37
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ public void testApiNamingConventions() throws Exception {
765765
"cluster.remote_info",
766766
"create",
767767
"get_script_context",
768+
"get_script_languages",
768769
"get_source",
769770
"indices.exists_type",
770771
"indices.get_upgrade",

modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java

+52-37
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import java.util.HashMap;
5656
import java.util.List;
5757
import java.util.Map;
58+
import java.util.Set;
59+
import java.util.function.Function;
5860

5961
/**
6062
* Provides the infrastructure for Lucene expressions as a scripting language for Elasticsearch.
@@ -65,6 +67,41 @@ public class ExpressionScriptEngine implements ScriptEngine {
6567

6668
public static final String NAME = "expression";
6769

70+
private static Map<ScriptContext<?>, Function<Expression,Object>> contexts = Map.of(
71+
BucketAggregationScript.CONTEXT,
72+
ExpressionScriptEngine::newBucketAggregationScriptFactory,
73+
74+
BucketAggregationSelectorScript.CONTEXT,
75+
(Expression expr) -> {
76+
BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr);
77+
BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) {
78+
@Override
79+
public boolean execute() {
80+
return factory.newInstance(getParams()).execute().doubleValue() == 1.0;
81+
}
82+
};
83+
return wrappedFactory;
84+
},
85+
86+
FilterScript.CONTEXT,
87+
(Expression expr) -> (FilterScript.Factory) (p, lookup) -> newFilterScript(expr, lookup, p),
88+
89+
ScoreScript.CONTEXT,
90+
(Expression expr) -> (ScoreScript.Factory) (p, lookup) -> newScoreScript(expr, lookup, p),
91+
92+
TermsSetQueryScript.CONTEXT,
93+
(Expression expr) -> (TermsSetQueryScript.Factory) (p, lookup) -> newTermsSetQueryScript(expr, lookup, p),
94+
95+
AggregationScript.CONTEXT,
96+
(Expression expr) -> (AggregationScript.Factory) (p, lookup) -> newAggregationScript(expr, lookup, p),
97+
98+
NumberSortScript.CONTEXT,
99+
(Expression expr) -> (NumberSortScript.Factory) (p, lookup) -> newSortScript(expr, lookup, p),
100+
101+
FieldScript.CONTEXT,
102+
(Expression expr) -> (FieldScript.Factory) (p, lookup) -> newFieldScript(expr, lookup, p)
103+
);
104+
68105
@Override
69106
public String getType() {
70107
return NAME;
@@ -102,37 +139,15 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
102139
}
103140
}
104141
});
105-
if (context.instanceClazz.equals(BucketAggregationScript.class)) {
106-
return context.factoryClazz.cast(newBucketAggregationScriptFactory(expr));
107-
} else if (context.instanceClazz.equals(BucketAggregationSelectorScript.class)) {
108-
BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr);
109-
BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) {
110-
@Override
111-
public boolean execute() {
112-
return factory.newInstance(getParams()).execute().doubleValue() == 1.0;
113-
}
114-
};
115-
return context.factoryClazz.cast(wrappedFactory);
116-
} else if (context.instanceClazz.equals(FilterScript.class)) {
117-
FilterScript.Factory factory = (p, lookup) -> newFilterScript(expr, lookup, p);
118-
return context.factoryClazz.cast(factory);
119-
} else if (context.instanceClazz.equals(ScoreScript.class)) {
120-
ScoreScript.Factory factory = (p, lookup) -> newScoreScript(expr, lookup, p);
121-
return context.factoryClazz.cast(factory);
122-
} else if (context.instanceClazz.equals(TermsSetQueryScript.class)) {
123-
TermsSetQueryScript.Factory factory = (p, lookup) -> newTermsSetQueryScript(expr, lookup, p);
124-
return context.factoryClazz.cast(factory);
125-
} else if (context.instanceClazz.equals(AggregationScript.class)) {
126-
AggregationScript.Factory factory = (p, lookup) -> newAggregationScript(expr, lookup, p);
127-
return context.factoryClazz.cast(factory);
128-
} else if (context.instanceClazz.equals(NumberSortScript.class)) {
129-
NumberSortScript.Factory factory = (p, lookup) -> newSortScript(expr, lookup, p);
130-
return context.factoryClazz.cast(factory);
131-
} else if (context.instanceClazz.equals(FieldScript.class)) {
132-
FieldScript.Factory factory = (p, lookup) -> newFieldScript(expr, lookup, p);
133-
return context.factoryClazz.cast(factory);
142+
if (contexts.containsKey(context) == false) {
143+
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
134144
}
135-
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
145+
return context.factoryClazz.cast(contexts.get(context).apply(expr));
146+
}
147+
148+
@Override
149+
public Set<ScriptContext<?>> getSupportedContexts() {
150+
return contexts.keySet();
136151
}
137152

138153
private static BucketAggregationScript.Factory newBucketAggregationScriptFactory(Expression expr) {
@@ -166,7 +181,7 @@ public Double execute() {
166181
};
167182
}
168183

169-
private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
184+
private static NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
170185
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
171186
// instead of complicating SimpleBindings (which should stay simple)
172187
SimpleBindings bindings = new SimpleBindings();
@@ -193,7 +208,7 @@ private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup
193208
return new ExpressionNumberSortScript(expr, bindings, needsScores);
194209
}
195210

196-
private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
211+
private static TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
197212
@Nullable Map<String, Object> vars) {
198213
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
199214
// instead of complicating SimpleBindings (which should stay simple)
@@ -216,7 +231,7 @@ private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr,
216231
return new ExpressionTermSetQueryScript(expr, bindings);
217232
}
218233

219-
private AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
234+
private static AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
220235
@Nullable Map<String, Object> vars) {
221236
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
222237
// instead of complicating SimpleBindings (which should stay simple)
@@ -252,7 +267,7 @@ private AggregationScript.LeafFactory newAggregationScript(Expression expr, Sear
252267
return new ExpressionAggregationScript(expr, bindings, needsScores, specialValue);
253268
}
254269

255-
private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
270+
private static FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
256271
SimpleBindings bindings = new SimpleBindings();
257272
for (String variable : expr.variables) {
258273
try {
@@ -273,7 +288,7 @@ private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup loo
273288
* This is a hack for filter scripts, which must return booleans instead of doubles as expression do.
274289
* See https://github.com/elastic/elasticsearch/issues/26429.
275290
*/
276-
private FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
291+
private static FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
277292
ScoreScript.LeafFactory searchLeafFactory = newScoreScript(expr, lookup, vars);
278293
return ctx -> {
279294
ScoreScript script = searchLeafFactory.newInstance(ctx);
@@ -290,7 +305,7 @@ public void setDocument(int docid) {
290305
};
291306
}
292307

293-
private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
308+
private static ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
294309
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
295310
// instead of complicating SimpleBindings (which should stay simple)
296311
SimpleBindings bindings = new SimpleBindings();
@@ -327,7 +342,7 @@ private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup loo
327342
/**
328343
* converts a ParseException at compile-time or link-time to a ScriptException
329344
*/
330-
private ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
345+
private static ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
331346
List<String> stack = new ArrayList<>();
332347
stack.add(portion);
333348
StringBuilder pointer = new StringBuilder();

modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/MustacheScriptEngine.java

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.security.PrivilegedAction;
4242
import java.util.Collections;
4343
import java.util.Map;
44+
import java.util.Set;
4445

4546
/**
4647
* Main entry point handling template registration, compilation and
@@ -79,6 +80,11 @@ public <T> T compile(String templateName, String templateSource, ScriptContext<T
7980

8081
}
8182

83+
@Override
84+
public Set<ScriptContext<?>> getSupportedContexts() {
85+
return Set.of(TemplateScript.CONTEXT);
86+
}
87+
8288
private CustomMustacheFactory createMustacheFactory(Map<String, String> options) {
8389
if (options == null || options.isEmpty() || options.containsKey(Script.CONTENT_TYPE_OPTION) == false) {
8490
return new CustomMustacheFactory();

modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java

+5
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ public Loader run() {
146146
}
147147
}
148148

149+
@Override
150+
public Set<ScriptContext<?>> getSupportedContexts() {
151+
return contextsToCompilers.keySet();
152+
}
153+
149154
/**
150155
* Generates a stateful factory class that will return script instances. Acts as a middle man between
151156
* the {@link ScriptContext#factoryClazz} and the {@link ScriptContext#instanceClazz} when used so that

plugins/examples/script-expert-scoring/src/main/java/org/elasticsearch/example/expertscript/ExpertScriptPlugin.java

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.io.UncheckedIOException;
3636
import java.util.Collection;
3737
import java.util.Map;
38+
import java.util.Set;
3839

3940
/**
4041
* An example script plugin that adds a {@link ScriptEngine} implementing expert scoring.
@@ -76,6 +77,11 @@ public void close() {
7677
// optionally close resources
7778
}
7879

80+
@Override
81+
public Set<ScriptContext<?>> getSupportedContexts() {
82+
return Set.of(ScoreScript.CONTEXT);
83+
}
84+
7985
private static class PureDfLeafFactory implements LeafFactory {
8086
private final Map<String, Object> params;
8187
private final SearchLookup lookup;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"get_script_languages":{
3+
"documentation":{
4+
"description":"Returns available script types, languages and contexts"
5+
},
6+
"stability":"experimental",
7+
"url":{
8+
"paths":[
9+
{
10+
"path":"/_script_language",
11+
"methods":[
12+
"GET"
13+
]
14+
}
15+
]
16+
},
17+
"params":{}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"Action to get script languages":
2+
- skip:
3+
version: " - 7.6.0"
4+
reason: "get_script_languages introduced in 7.6.0"
5+
- do:
6+
get_script_languages: {}
7+
8+
- match: { types_allowed.0: "inline" }
9+
- match: { types_allowed.1: "stored" }

server/src/main/java/org/elasticsearch/action/ActionModule.java

+5
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@
8080
import org.elasticsearch.action.admin.cluster.stats.TransportClusterStatsAction;
8181
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptAction;
8282
import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptContextAction;
83+
import org.elasticsearch.action.admin.cluster.storedscripts.GetScriptLanguageAction;
8384
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptAction;
8485
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptAction;
8586
import org.elasticsearch.action.admin.cluster.storedscripts.TransportDeleteStoredScriptAction;
8687
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetScriptContextAction;
88+
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetScriptLanguageAction;
8789
import org.elasticsearch.action.admin.cluster.storedscripts.TransportGetStoredScriptAction;
8890
import org.elasticsearch.action.admin.cluster.storedscripts.TransportPutStoredScriptAction;
8991
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction;
@@ -249,6 +251,7 @@
249251
import org.elasticsearch.rest.action.admin.cluster.RestDeleteStoredScriptAction;
250252
import org.elasticsearch.rest.action.admin.cluster.RestGetRepositoriesAction;
251253
import org.elasticsearch.rest.action.admin.cluster.RestGetScriptContextAction;
254+
import org.elasticsearch.rest.action.admin.cluster.RestGetScriptLanguageAction;
252255
import org.elasticsearch.rest.action.admin.cluster.RestGetSnapshotsAction;
253256
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
254257
import org.elasticsearch.rest.action.admin.cluster.RestGetTaskAction;
@@ -522,6 +525,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
522525
actions.register(GetStoredScriptAction.INSTANCE, TransportGetStoredScriptAction.class);
523526
actions.register(DeleteStoredScriptAction.INSTANCE, TransportDeleteStoredScriptAction.class);
524527
actions.register(GetScriptContextAction.INSTANCE, TransportGetScriptContextAction.class);
528+
actions.register(GetScriptLanguageAction.INSTANCE, TransportGetScriptLanguageAction.class);
525529

526530
actions.register(FieldCapabilitiesAction.INSTANCE, TransportFieldCapabilitiesAction.class);
527531
actions.register(TransportFieldCapabilitiesIndexAction.TYPE, TransportFieldCapabilitiesIndexAction.class);
@@ -662,6 +666,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
662666
registerHandler.accept(new RestPutStoredScriptAction(restController));
663667
registerHandler.accept(new RestDeleteStoredScriptAction(restController));
664668
registerHandler.accept(new RestGetScriptContextAction(restController));
669+
registerHandler.accept(new RestGetScriptLanguageAction(restController));
665670

666671
registerHandler.accept(new RestFieldCapabilitiesAction(restController));
667672

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.storedscripts;
21+
22+
import org.elasticsearch.action.ActionType;
23+
24+
public class GetScriptLanguageAction extends ActionType<GetScriptLanguageResponse> {
25+
public static final GetScriptLanguageAction INSTANCE = new GetScriptLanguageAction();
26+
public static final String NAME = "cluster:admin/script_language/get";
27+
28+
private GetScriptLanguageAction() {
29+
super(NAME, GetScriptLanguageResponse::new);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.admin.cluster.storedscripts;
21+
22+
import org.elasticsearch.action.ActionRequest;
23+
import org.elasticsearch.action.ActionRequestValidationException;
24+
import org.elasticsearch.common.io.stream.StreamInput;
25+
26+
import java.io.IOException;
27+
28+
public class GetScriptLanguageRequest extends ActionRequest {
29+
public GetScriptLanguageRequest() {
30+
super();
31+
}
32+
33+
GetScriptLanguageRequest(StreamInput in) throws IOException {
34+
super(in);
35+
}
36+
37+
@Override
38+
public ActionRequestValidationException validate() { return null; }
39+
40+
@Override
41+
public String toString() { return "get script languages"; }
42+
}

0 commit comments

Comments
 (0)