Skip to content

Commit 5b5e617

Browse files
committed
Scripting: add available languages & contexts API
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: elastic#49463
1 parent 7a7d15b commit 5b5e617

File tree

18 files changed

+761
-37
lines changed

18 files changed

+761
-37
lines changed

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

+51-37
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.util.HashMap;
5656
import java.util.List;
5757
import java.util.Map;
58+
import java.util.function.Function;
5859

5960
/**
6061
* Provides the infrastructure for Lucene expressions as a scripting language for Elasticsearch.
@@ -65,6 +66,41 @@ public class ExpressionScriptEngine implements ScriptEngine {
6566

6667
public static final String NAME = "expression";
6768

69+
private static Map<ScriptContext<?>, Function<Expression,Object>> contexts = Map.of(
70+
BucketAggregationScript.CONTEXT,
71+
ExpressionScriptEngine::newBucketAggregationScriptFactory,
72+
73+
BucketAggregationSelectorScript.CONTEXT,
74+
(Expression expr) -> {
75+
BucketAggregationScript.Factory factory = newBucketAggregationScriptFactory(expr);
76+
BucketAggregationSelectorScript.Factory wrappedFactory = parameters -> new BucketAggregationSelectorScript(parameters) {
77+
@Override
78+
public boolean execute() {
79+
return factory.newInstance(getParams()).execute().doubleValue() == 1.0;
80+
}
81+
};
82+
return wrappedFactory;
83+
},
84+
85+
FilterScript.CONTEXT,
86+
(Expression expr) -> (FilterScript.Factory) (p, lookup) -> newFilterScript(expr, lookup, p),
87+
88+
ScoreScript.CONTEXT,
89+
(Expression expr) -> (ScoreScript.Factory) (p, lookup) -> newScoreScript(expr, lookup, p),
90+
91+
TermsSetQueryScript.CONTEXT,
92+
(Expression expr) -> (TermsSetQueryScript.Factory) (p, lookup) -> newTermsSetQueryScript(expr, lookup, p),
93+
94+
AggregationScript.CONTEXT,
95+
(Expression expr) -> (AggregationScript.Factory) (p, lookup) -> newAggregationScript(expr, lookup, p),
96+
97+
NumberSortScript.CONTEXT,
98+
(Expression expr) -> (NumberSortScript.Factory) (p, lookup) -> newSortScript(expr, lookup, p),
99+
100+
FieldScript.CONTEXT,
101+
(Expression expr) -> (FieldScript.Factory) (p, lookup) -> newFieldScript(expr, lookup, p)
102+
);
103+
68104
@Override
69105
public String getType() {
70106
return NAME;
@@ -102,37 +138,15 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
102138
}
103139
}
104140
});
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);
141+
if (contexts.containsKey(context) == false) {
142+
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
134143
}
135-
throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]");
144+
return context.factoryClazz.cast(contexts.get(context).apply(expr));
145+
}
146+
147+
@Override
148+
public List<ScriptContext<?>> getSupportedContexts() {
149+
return new ArrayList<>(contexts.keySet());
136150
}
137151

138152
private static BucketAggregationScript.Factory newBucketAggregationScriptFactory(Expression expr) {
@@ -166,7 +180,7 @@ public Double execute() {
166180
};
167181
}
168182

169-
private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
183+
private static NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
170184
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
171185
// instead of complicating SimpleBindings (which should stay simple)
172186
SimpleBindings bindings = new SimpleBindings();
@@ -193,7 +207,7 @@ private NumberSortScript.LeafFactory newSortScript(Expression expr, SearchLookup
193207
return new ExpressionNumberSortScript(expr, bindings, needsScores);
194208
}
195209

196-
private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
210+
private static TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr, SearchLookup lookup,
197211
@Nullable Map<String, Object> vars) {
198212
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
199213
// instead of complicating SimpleBindings (which should stay simple)
@@ -216,7 +230,7 @@ private TermsSetQueryScript.LeafFactory newTermsSetQueryScript(Expression expr,
216230
return new ExpressionTermSetQueryScript(expr, bindings);
217231
}
218232

219-
private AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
233+
private static AggregationScript.LeafFactory newAggregationScript(Expression expr, SearchLookup lookup,
220234
@Nullable Map<String, Object> vars) {
221235
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
222236
// instead of complicating SimpleBindings (which should stay simple)
@@ -252,7 +266,7 @@ private AggregationScript.LeafFactory newAggregationScript(Expression expr, Sear
252266
return new ExpressionAggregationScript(expr, bindings, needsScores, specialValue);
253267
}
254268

255-
private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
269+
private static FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
256270
SimpleBindings bindings = new SimpleBindings();
257271
for (String variable : expr.variables) {
258272
try {
@@ -273,7 +287,7 @@ private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup loo
273287
* This is a hack for filter scripts, which must return booleans instead of doubles as expression do.
274288
* See https://github.com/elastic/elasticsearch/issues/26429.
275289
*/
276-
private FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
290+
private static FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
277291
ScoreScript.LeafFactory searchLeafFactory = newScoreScript(expr, lookup, vars);
278292
return ctx -> {
279293
ScoreScript script = searchLeafFactory.newInstance(ctx);
@@ -290,7 +304,7 @@ public void setDocument(int docid) {
290304
};
291305
}
292306

293-
private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
307+
private static ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
294308
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
295309
// instead of complicating SimpleBindings (which should stay simple)
296310
SimpleBindings bindings = new SimpleBindings();
@@ -327,7 +341,7 @@ private ScoreScript.LeafFactory newScoreScript(Expression expr, SearchLookup loo
327341
/**
328342
* converts a ParseException at compile-time or link-time to a ScriptException
329343
*/
330-
private ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
344+
private static ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {
331345
List<String> stack = new ArrayList<>();
332346
stack.add(portion);
333347
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
@@ -40,6 +40,7 @@
4040
import java.security.AccessController;
4141
import java.security.PrivilegedAction;
4242
import java.util.Collections;
43+
import java.util.List;
4344
import java.util.Map;
4445

4546
/**
@@ -79,6 +80,11 @@ public <T> T compile(String templateName, String templateSource, ScriptContext<T
7980

8081
}
8182

83+
@Override
84+
public List<ScriptContext<?>> getSupportedContexts() {
85+
return List.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 List<ScriptContext<?>> getSupportedContexts() {
151+
return new ArrayList<>(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
@@ -34,6 +34,7 @@
3434
import java.io.IOException;
3535
import java.io.UncheckedIOException;
3636
import java.util.Collection;
37+
import java.util.List;
3738
import java.util.Map;
3839

3940
/**
@@ -76,6 +77,11 @@ public void close() {
7677
// optionally close resources
7778
}
7879

80+
@Override
81+
public List<ScriptContext<?>> getSupportedContexts() {
82+
return List.of(ScoreScript.CONTEXT);
83+
}
84+
7985
private static class PureDfLeafFactory implements LeafFactory {
8086
private final Map<String, Object> params;
8187
private final SearchLookup lookup;

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)