Updates response for `GET /_script_context`, returning a `contexts` object with a list of context description objects. The description includes the context name and a list of methods available. The methods list has the signature for the `execute` mathod and any getters. eg. ``` { "contexts": [ { "name" : "moving-function", "methods" : [ { "name" : "execute", "return_type" : "double", "params" : [ { "type" : "java.util.Map", "name" : "params" }, { "type" : "double[]", "name" : "values" } ] } ] }, { "name" : "number_sort", "methods" : [ { "name" : "execute", "return_type" : "double", "params" : [ ] }, { "name" : "getDoc", "return_type" : "java.util.Map", "params" : [ ] }, { "name" : "getParams", "return_type" : "java.util.Map", "params" : [ ] }, { "name" : "get_score", "return_type" : "double", "params" : [ ] } ] }, ... ] } ``` fixes: #47411
This commit is contained in:
parent
50d7424e7d
commit
55d00cf2b1
|
@ -4,24 +4,7 @@
|
|||
reason: "get_all_contexts introduced in 7.6.0"
|
||||
- do:
|
||||
get_script_context: {}
|
||||
- match: { contexts.aggregation_selector: {} }
|
||||
- match: { contexts.aggs: {} }
|
||||
- match: { contexts.aggs_combine: {} }
|
||||
- match: { contexts.aggs_init: {} }
|
||||
- match: { contexts.aggs_map: {} }
|
||||
- match: { contexts.aggs_reduce: {} }
|
||||
- match: { contexts.bucket_aggregation: {} }
|
||||
- match: { contexts.field: {} }
|
||||
- match: { contexts.filter: {} }
|
||||
- match: { contexts.ingest: {} }
|
||||
- match: { contexts.interval: {} }
|
||||
- match: { contexts.number_sort: {} }
|
||||
- match: { contexts.processor_conditional: {} }
|
||||
- match: { contexts.score: {} }
|
||||
- match: { contexts.script_heuristic: {} }
|
||||
- match: { contexts.similarity: {} }
|
||||
- match: { contexts.similarity_weight: {} }
|
||||
- match: { contexts.string_sort: {} }
|
||||
- match: { contexts.template: {} }
|
||||
- match: { contexts.terms_set: {} }
|
||||
- match: { contexts.update: {} }
|
||||
|
||||
- is_true: contexts.0.name
|
||||
- is_true: contexts.0.methods.0.return_type
|
||||
- match: { contexts.0.methods.0.name: "execute" }
|
||||
|
|
|
@ -28,69 +28,72 @@ import org.elasticsearch.common.xcontent.StatusToXContentObject;
|
|||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.script.ScriptContextInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentParser.Token.END_OBJECT;
|
||||
import static org.elasticsearch.common.xcontent.XContentParser.Token.START_OBJECT;
|
||||
|
||||
public class GetScriptContextResponse extends ActionResponse implements StatusToXContentObject {
|
||||
|
||||
private static final ParseField CONTEXTS = new ParseField("contexts");
|
||||
private final List<String> contextNames;
|
||||
final Map<String,ScriptContextInfo> contexts;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final ConstructingObjectParser<GetScriptContextResponse,Void> PARSER =
|
||||
new ConstructingObjectParser<>("get_script_context", true,
|
||||
(a) -> {
|
||||
Map<String, Object> contexts = ((List<String>) a[0]).stream().collect(Collectors.toMap(
|
||||
name -> name, name -> new Object()
|
||||
));
|
||||
Map<String,ScriptContextInfo> contexts = ((List<ScriptContextInfo>)a[0]).stream().collect(
|
||||
Collectors.toMap(ScriptContextInfo::getName, c -> c)
|
||||
);
|
||||
return new GetScriptContextResponse(contexts);
|
||||
}
|
||||
);
|
||||
|
||||
static {
|
||||
PARSER.declareNamedObjects(
|
||||
ConstructingObjectParser.constructorArg(),
|
||||
(p, c, n) ->
|
||||
{
|
||||
// advance empty object
|
||||
assert(p.nextToken() == START_OBJECT);
|
||||
assert(p.nextToken() == END_OBJECT);
|
||||
return n;
|
||||
},
|
||||
CONTEXTS
|
||||
);
|
||||
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
|
||||
(parser, ctx) -> ScriptContextInfo.PARSER.apply(parser, ctx), CONTEXTS);
|
||||
}
|
||||
|
||||
GetScriptContextResponse(StreamInput in) throws IOException {
|
||||
super(in);
|
||||
int size = in.readInt();
|
||||
ArrayList<String> contextNames = new ArrayList<>(size);
|
||||
HashMap<String, ScriptContextInfo> contexts = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
contextNames.add(in.readString());
|
||||
ScriptContextInfo info = new ScriptContextInfo(in);
|
||||
contexts.put(info.name, info);
|
||||
}
|
||||
this.contextNames = Collections.unmodifiableList(contextNames);
|
||||
this.contexts = Collections.unmodifiableMap(contexts);
|
||||
}
|
||||
|
||||
GetScriptContextResponse(Map<String,Object> contexts) {
|
||||
List<String> contextNames = new ArrayList<>(contexts.keySet());
|
||||
contextNames.sort(String::compareTo);
|
||||
this.contextNames = Collections.unmodifiableList(contextNames);
|
||||
// TransportAction constructor
|
||||
GetScriptContextResponse(Set<ScriptContextInfo> contexts) {
|
||||
this.contexts = Collections.unmodifiableMap(contexts.stream().collect(
|
||||
Collectors.toMap(ScriptContextInfo::getName, Function.identity())
|
||||
));
|
||||
}
|
||||
|
||||
// Parser constructor
|
||||
private GetScriptContextResponse(Map<String,ScriptContextInfo> contexts) {
|
||||
this.contexts = Collections.unmodifiableMap(contexts);
|
||||
}
|
||||
|
||||
private List<ScriptContextInfo> byName() {
|
||||
return contexts.values().stream().sorted(Comparator.comparing(ScriptContextInfo::getName)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeInt(this.contextNames.size());
|
||||
for (String context: this.contextNames) {
|
||||
out.writeString(context);
|
||||
out.writeInt(contexts.size());
|
||||
for (ScriptContextInfo context: contexts.values()) {
|
||||
context.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,11 +104,11 @@ public class GetScriptContextResponse extends ActionResponse implements StatusTo
|
|||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject().startObject(CONTEXTS.getPreferredName());
|
||||
for (String contextName: this.contextNames) {
|
||||
builder.startObject(contextName).endObject();
|
||||
builder.startObject().startArray(CONTEXTS.getPreferredName());
|
||||
for (ScriptContextInfo context: byName()) {
|
||||
context.toXContent(builder, params);
|
||||
}
|
||||
builder.endObject().endObject(); // CONTEXTS
|
||||
builder.endArray().endObject(); // CONTEXTS
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
@ -122,11 +125,11 @@ public class GetScriptContextResponse extends ActionResponse implements StatusTo
|
|||
return false;
|
||||
}
|
||||
GetScriptContextResponse that = (GetScriptContextResponse) o;
|
||||
return contextNames.equals(that.contextNames);
|
||||
return contexts.equals(that.contexts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(contextNames);
|
||||
return Objects.hash(contexts);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,12 @@ import org.elasticsearch.action.ActionListener;
|
|||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.script.ScriptContextInfo;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.tasks.Task;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Set;
|
||||
|
||||
public class TransportGetScriptContextAction extends HandledTransportAction<GetScriptContextRequest, GetScriptContextResponse> {
|
||||
|
||||
|
@ -41,9 +41,7 @@ public class TransportGetScriptContextAction extends HandledTransportAction<GetS
|
|||
|
||||
@Override
|
||||
protected void doExecute(Task task, GetScriptContextRequest request, ActionListener<GetScriptContextResponse> listener) {
|
||||
Map<String,Object> contexts = scriptService.getContextNames().stream().collect(
|
||||
Collectors.toMap(name -> name, name -> new Object())
|
||||
);
|
||||
Set<ScriptContextInfo> contexts = scriptService.getContextInfos();
|
||||
listener.onResponse(new GetScriptContextResponse(contexts));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
|
||||
public class ScriptContextInfo implements ToXContentObject, Writeable {
|
||||
public final String name;
|
||||
public final ScriptMethodInfo execute;
|
||||
public final Set<ScriptMethodInfo> getters;
|
||||
|
||||
private static final String NAME_FIELD = "name";
|
||||
private static final String METHODS_FIELD = "methods";
|
||||
|
||||
// ScriptService constructor
|
||||
ScriptContextInfo(String name, Class<?> clazz) {
|
||||
this.name = name;
|
||||
this.execute = ScriptMethodInfo.executeFromContext(clazz);
|
||||
this.getters = Collections.unmodifiableSet(ScriptMethodInfo.gettersFromContext(clazz));
|
||||
}
|
||||
|
||||
// Deserialization constructor
|
||||
ScriptContextInfo(String name, List<ScriptMethodInfo> methods) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(methods);
|
||||
|
||||
String executeName = "execute";
|
||||
String getName = "get";
|
||||
// ignored instead of error, so future implementations can add methods. Same as ScriptContextInfo(String, Class).
|
||||
String otherName = "other";
|
||||
Map<String, List<ScriptMethodInfo>> methodTypes = methods.stream().collect(Collectors.groupingBy(
|
||||
m -> {
|
||||
if (m.name.equals(executeName)) {
|
||||
return executeName;
|
||||
} else if (m.name.startsWith(getName) && m.parameters.size() == 0) {
|
||||
return getName;
|
||||
}
|
||||
return otherName;
|
||||
}
|
||||
));
|
||||
|
||||
if (methodTypes.containsKey(executeName) == false) {
|
||||
throw new IllegalArgumentException("Could not find required method [" + executeName + "] in [" + name + "], found " +
|
||||
methods.stream().map(m -> m.name).sorted().collect(Collectors.joining(", ", "[", "]")));
|
||||
} else if ((methodTypes.get(executeName).size() != 1)) {
|
||||
throw new IllegalArgumentException("Cannot have multiple [execute] methods in [" + name + "], found [" +
|
||||
methodTypes.get(executeName).size() + "]"
|
||||
);
|
||||
}
|
||||
this.execute = methodTypes.get(executeName).get(0);
|
||||
|
||||
if (methodTypes.containsKey(getName)) {
|
||||
this.getters = Collections.unmodifiableSet(new HashSet<>(methodTypes.get(getName)));
|
||||
} else {
|
||||
this.getters = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
// Test constructor
|
||||
public ScriptContextInfo(String name, ScriptMethodInfo execute, Set<ScriptMethodInfo> getters) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.execute = Objects.requireNonNull(execute);
|
||||
this.getters = Objects.requireNonNull(getters);
|
||||
}
|
||||
|
||||
public ScriptContextInfo(StreamInput in) throws IOException {
|
||||
this.name = in.readString();
|
||||
this.execute = new ScriptMethodInfo(in);
|
||||
int numGetters = in.readInt();
|
||||
Set<ScriptMethodInfo> getters = new HashSet<>(numGetters);
|
||||
for (int i = 0; i < numGetters; i++) {
|
||||
getters.add(new ScriptMethodInfo(in));
|
||||
}
|
||||
this.getters = Collections.unmodifiableSet(getters);
|
||||
}
|
||||
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(name);
|
||||
execute.writeTo(out);
|
||||
out.writeInt(getters.size());
|
||||
for (ScriptMethodInfo getter: getters) {
|
||||
getter.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public List<ScriptMethodInfo> methods() {
|
||||
ArrayList<ScriptMethodInfo> methods = new ArrayList<>();
|
||||
methods.add(this.execute);
|
||||
methods.addAll(this.getters);
|
||||
return Collections.unmodifiableList(methods);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ConstructingObjectParser<ScriptContextInfo,Void> PARSER =
|
||||
new ConstructingObjectParser<>("script_context_info", true,
|
||||
(m, name) -> new ScriptContextInfo((String) m[0], (List<ScriptMethodInfo>) m[1])
|
||||
);
|
||||
|
||||
static {
|
||||
PARSER.declareString(constructorArg(), new ParseField(NAME_FIELD));
|
||||
PARSER.declareObjectArray(constructorArg(),
|
||||
(parser, ctx) -> ScriptMethodInfo.PARSER.apply(parser, ctx),
|
||||
new ParseField(METHODS_FIELD));
|
||||
}
|
||||
|
||||
public static ScriptContextInfo fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ScriptContextInfo that = (ScriptContextInfo) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Objects.equals(execute, that.execute) &&
|
||||
Objects.equals(getters, that.getters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, execute, getters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject().field(NAME_FIELD, name).startArray(METHODS_FIELD);
|
||||
execute.toXContent(builder, params);
|
||||
for (ScriptMethodInfo method: getters.stream().sorted(Comparator.comparing(g -> g.name)).collect(Collectors.toList())) {
|
||||
method.toXContent(builder, params);
|
||||
}
|
||||
return builder.endArray().endObject();
|
||||
}
|
||||
|
||||
public static class ScriptMethodInfo implements ToXContentObject, Writeable {
|
||||
public final String name, returnType;
|
||||
public final List<ParameterInfo> parameters;
|
||||
|
||||
static final String RETURN_TYPE_FIELD = "return_type";
|
||||
static final String PARAMETERS_FIELD = "params";
|
||||
|
||||
public ScriptMethodInfo(String name, String returnType, List<ParameterInfo> parameters) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.returnType = Objects.requireNonNull(returnType);
|
||||
this.parameters = Collections.unmodifiableList(Objects.requireNonNull(parameters));
|
||||
}
|
||||
|
||||
public ScriptMethodInfo(StreamInput in) throws IOException {
|
||||
this.name = in.readString();
|
||||
this.returnType = in.readString();
|
||||
int numParameters = in.readInt();
|
||||
ArrayList<ParameterInfo> parameters = new ArrayList<>(numParameters);
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
parameters.add(new ParameterInfo(in));
|
||||
}
|
||||
this.parameters = Collections.unmodifiableList(parameters);
|
||||
}
|
||||
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(name);
|
||||
out.writeString(returnType);
|
||||
out.writeInt(parameters.size());
|
||||
for (ParameterInfo parameter: parameters) {
|
||||
parameter.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ConstructingObjectParser<ScriptMethodInfo,Void> PARSER =
|
||||
new ConstructingObjectParser<>("method", true,
|
||||
(m, name) -> new ScriptMethodInfo((String) m[0], (String) m[1], (List<ParameterInfo>) m[2])
|
||||
);
|
||||
|
||||
static {
|
||||
PARSER.declareString(constructorArg(), new ParseField(NAME_FIELD));
|
||||
PARSER.declareString(constructorArg(), new ParseField(RETURN_TYPE_FIELD));
|
||||
PARSER.declareObjectArray(constructorArg(),
|
||||
(parser, ctx) -> ParameterInfo.PARSER.apply(parser, ctx),
|
||||
new ParseField(PARAMETERS_FIELD));
|
||||
}
|
||||
|
||||
public static ScriptMethodInfo fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ScriptMethodInfo that = (ScriptMethodInfo) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Objects.equals(returnType, that.returnType) &&
|
||||
Objects.equals(parameters, that.parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, returnType, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject().field(NAME_FIELD, name).field(RETURN_TYPE_FIELD, returnType).startArray(PARAMETERS_FIELD);
|
||||
for (ParameterInfo parameter: parameters) {
|
||||
parameter.toXContent(builder, params);
|
||||
}
|
||||
return builder.endArray().endObject();
|
||||
}
|
||||
|
||||
public static class ParameterInfo implements ToXContentObject, Writeable {
|
||||
public final String type, name;
|
||||
|
||||
public static final String TYPE_FIELD = "type";
|
||||
|
||||
public ParameterInfo(String type, String name) {
|
||||
this.type = Objects.requireNonNull(type);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
}
|
||||
|
||||
public ParameterInfo(StreamInput in) throws IOException {
|
||||
this.type = in.readString();
|
||||
this.name = in.readString();
|
||||
}
|
||||
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(type);
|
||||
out.writeString(name);
|
||||
}
|
||||
|
||||
private static ConstructingObjectParser<ParameterInfo,Void> PARSER =
|
||||
new ConstructingObjectParser<>("parameters", true,
|
||||
(p) -> new ParameterInfo((String)p[0], (String)p[1])
|
||||
);
|
||||
|
||||
static {
|
||||
PARSER.declareString(constructorArg(), new ParseField(TYPE_FIELD));
|
||||
PARSER.declareString(constructorArg(), new ParseField(NAME_FIELD));
|
||||
}
|
||||
|
||||
public static ParameterInfo fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject().field(TYPE_FIELD, this.type).field(NAME_FIELD, this.name).endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ParameterInfo that = (ParameterInfo) o;
|
||||
return Objects.equals(type, that.type) &&
|
||||
Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, name);
|
||||
}
|
||||
}
|
||||
|
||||
static ScriptMethodInfo executeFromContext(Class<?> clazz) {
|
||||
Method execute = null;
|
||||
String name = "execute";
|
||||
|
||||
// See ScriptContext.findMethod
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (method.getName().equals(name)) {
|
||||
if (execute != null) {
|
||||
throw new IllegalArgumentException("Cannot have multiple [" + name + "] methods on class [" +
|
||||
clazz.getName() + "]");
|
||||
}
|
||||
execute = method;
|
||||
}
|
||||
}
|
||||
if (execute == null) {
|
||||
throw new IllegalArgumentException("Could not find required method [" + name + "] on class [" + clazz.getName() + "]");
|
||||
}
|
||||
|
||||
Class<?> returnTypeClazz = execute.getReturnType();
|
||||
String returnType = returnTypeClazz.getTypeName();
|
||||
|
||||
Class<?>[] parameterTypes = execute.getParameterTypes();
|
||||
List<ParameterInfo> parameters = new ArrayList<>();
|
||||
if (parameterTypes.length > 0) {
|
||||
// TODO(stu): ensure empty/no PARAMETERS if parameterTypes.length == 0?
|
||||
String parametersFieldName = "PARAMETERS";
|
||||
|
||||
// See ScriptClassInfo.readArgumentNamesConstant
|
||||
Field parameterNamesField;
|
||||
try {
|
||||
parameterNamesField = clazz.getField(parametersFieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new IllegalArgumentException("Could not find field [" + parametersFieldName + "] on instance class [" +
|
||||
clazz.getName() + "] but method [" + name + "] has [" + parameterTypes.length + "] parameters");
|
||||
}
|
||||
if (!parameterNamesField.getType().equals(String[].class)) {
|
||||
throw new IllegalArgumentException("Expected a constant [String[] PARAMETERS] on instance class [" +
|
||||
clazz.getName() + "] for method [" + name + "] with [" + parameterTypes.length + "] parameters, found [" +
|
||||
parameterNamesField.getType().getTypeName() + "]");
|
||||
}
|
||||
|
||||
String[] argumentNames;
|
||||
try {
|
||||
argumentNames = (String[]) parameterNamesField.get(null);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Error trying to read [" + clazz.getName() + "#ARGUMENTS]", e);
|
||||
}
|
||||
|
||||
if (argumentNames.length != parameterTypes.length) {
|
||||
throw new IllegalArgumentException("Expected argument names [" + argumentNames.length +
|
||||
"] to have the same arity [" + parameterTypes.length + "] for method [" + name +
|
||||
"] of class [" + clazz.getName() + "]");
|
||||
}
|
||||
|
||||
for (int i = 0; i < argumentNames.length; i++) {
|
||||
parameters.add(new ParameterInfo(parameterTypes[i].getTypeName(), argumentNames[i]));
|
||||
}
|
||||
}
|
||||
return new ScriptMethodInfo(name, returnType, parameters);
|
||||
}
|
||||
|
||||
static Set<ScriptMethodInfo> gettersFromContext(Class<?> clazz) {
|
||||
// See ScriptClassInfo(PainlessLookup painlessLookup, Class<?> baseClass)
|
||||
HashSet<ScriptMethodInfo> getters = new HashSet<>();
|
||||
for (java.lang.reflect.Method m : clazz.getMethods()) {
|
||||
if (!m.isDefault() &&
|
||||
m.getName().startsWith("get") &&
|
||||
!m.getName().equals("getClass") &&
|
||||
!Modifier.isStatic(m.getModifiers()) &&
|
||||
m.getParameters().length == 0) {
|
||||
getters.add(new ScriptMethodInfo(m.getName(), m.getReturnType().getTypeName(), new ArrayList<>()));
|
||||
}
|
||||
}
|
||||
return getters;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -538,8 +538,12 @@ public class ScriptService implements Closeable, ClusterStateApplier {
|
|||
}
|
||||
}
|
||||
|
||||
public Set<String> getContextNames() {
|
||||
return contexts.keySet();
|
||||
public Set<ScriptContextInfo> getContextInfos() {
|
||||
Set<ScriptContextInfo> infos = new HashSet<ScriptContextInfo>(contexts.size());
|
||||
for (ScriptContext<?> context : contexts.values()) {
|
||||
infos.add(new ScriptContextInfo(context.name, context.instanceClazz));
|
||||
}
|
||||
return infos;
|
||||
}
|
||||
|
||||
public ScriptStats stats() {
|
||||
|
|
|
@ -24,24 +24,15 @@ import org.elasticsearch.test.AbstractSerializingTestCase;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiLettersOfLengthBetween;
|
||||
|
||||
public class GetScriptContextResponseTests extends AbstractSerializingTestCase<GetScriptContextResponse> {
|
||||
|
||||
@Override
|
||||
protected GetScriptContextResponse createTestInstance() {
|
||||
if (randomBoolean()) {
|
||||
return new GetScriptContextResponse(Collections.emptyMap());
|
||||
return new GetScriptContextResponse(Collections.emptySet());
|
||||
}
|
||||
Map<String,Object> items = new HashMap<>();
|
||||
for (int i = randomIntBetween(1, 10); i > 0; i--) {
|
||||
items.put(randomAsciiLettersOfLengthBetween(1, 16), new Object());
|
||||
}
|
||||
return new GetScriptContextResponse(items);
|
||||
|
||||
return new GetScriptContextResponse(ScriptContextInfoSerializingTests.randomInstances());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -56,10 +47,6 @@ public class GetScriptContextResponseTests extends AbstractSerializingTestCase<G
|
|||
|
||||
@Override
|
||||
protected GetScriptContextResponse mutateInstance(GetScriptContextResponse instance) throws IOException {
|
||||
Map<String,Object> items = new HashMap<>();
|
||||
for (int i = randomIntBetween(1, 10); i > 0; i--) {
|
||||
items.put(randomAsciiLettersOfLengthBetween(1, 16), new Object());
|
||||
}
|
||||
return new GetScriptContextResponse(items);
|
||||
return new GetScriptContextResponse(ScriptContextInfoSerializingTests.mutateOne(instance.contexts.values()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.action.admin.cluster.storedscripts;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ScriptContextInfo;
|
||||
import org.elasticsearch.test.AbstractSerializingTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ScriptContextInfoSerializingTests extends AbstractSerializingTestCase<ScriptContextInfo> {
|
||||
private static final int MIN_LENGTH = 1;
|
||||
private static final int MAX_LENGTH = 16;
|
||||
|
||||
@Override
|
||||
protected ScriptContextInfo doParseInstance(XContentParser parser) throws IOException {
|
||||
return ScriptContextInfo.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScriptContextInfo createTestInstance() {
|
||||
return randomInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Writeable.Reader<ScriptContextInfo> instanceReader() { return ScriptContextInfo::new; }
|
||||
|
||||
|
||||
@Override
|
||||
protected ScriptContextInfo mutateInstance(ScriptContextInfo instance) throws IOException {
|
||||
return mutate(instance, null);
|
||||
}
|
||||
|
||||
private static ScriptContextInfo mutate(ScriptContextInfo instance, Set<String> names) {
|
||||
if (names == null) { names = new HashSet<>(); }
|
||||
switch (randomIntBetween(0, 2)) {
|
||||
case 0:
|
||||
return new ScriptContextInfo(
|
||||
randomValueOtherThanMany(names::contains, () -> randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH)),
|
||||
instance.execute,
|
||||
instance.getters
|
||||
);
|
||||
case 1:
|
||||
return new ScriptContextInfo(
|
||||
instance.name,
|
||||
ScriptMethodInfoSerializingTests.mutate(instance.execute),
|
||||
instance.getters
|
||||
);
|
||||
default:
|
||||
return new ScriptContextInfo(
|
||||
instance.name,
|
||||
instance.execute,
|
||||
ScriptMethodInfoSerializingTests.mutateOneGetter(instance.getters)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Set<ScriptContextInfo> mutateOne(Collection<ScriptContextInfo> instances) {
|
||||
if (instances.size() == 0) {
|
||||
return Collections.singleton(randomInstance());
|
||||
}
|
||||
ArrayList<ScriptContextInfo> mutated = new ArrayList<>(instances);
|
||||
int mutateIndex = randomIntBetween(0, instances.size() - 1);
|
||||
mutated.set(mutateIndex, mutate(mutated.get(mutateIndex), instances.stream().map(i -> i.name).collect(Collectors.toSet())));
|
||||
return Collections.unmodifiableSet(new HashSet<>(mutated));
|
||||
}
|
||||
|
||||
static ScriptContextInfo randomInstance() {
|
||||
return new ScriptContextInfo(
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
ScriptMethodInfoSerializingTests.randomInstance(ScriptMethodInfoSerializingTests.NameType.EXECUTE),
|
||||
ScriptMethodInfoSerializingTests.randomGetterInstances()
|
||||
);
|
||||
}
|
||||
|
||||
static Set<ScriptContextInfo> randomInstances() {
|
||||
Set<String> names = new HashSet<>();
|
||||
int size = randomIntBetween(0, MAX_LENGTH);
|
||||
HashSet<ScriptContextInfo> instances = new HashSet<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String name = randomValueOtherThanMany(names::contains, () -> randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH));
|
||||
names.add(name);
|
||||
instances.add(new ScriptContextInfo(
|
||||
name,
|
||||
ScriptMethodInfoSerializingTests.randomInstance(ScriptMethodInfoSerializingTests.NameType.EXECUTE),
|
||||
ScriptMethodInfoSerializingTests.randomGetterInstances()
|
||||
));
|
||||
}
|
||||
return Collections.unmodifiableSet(instances);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.action.admin.cluster.storedscripts;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ScriptContextInfo.ScriptMethodInfo;
|
||||
import org.elasticsearch.test.AbstractSerializingTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScriptMethodInfoSerializingTests extends AbstractSerializingTestCase<ScriptMethodInfo> {
|
||||
private static final String EXECUTE = "execute";
|
||||
private static final String GET_PREFIX = "get";
|
||||
private static final int MIN_LENGTH = 1;
|
||||
private static final int MAX_LENGTH = 16;
|
||||
|
||||
enum NameType {
|
||||
EXECUTE,
|
||||
GETTER,
|
||||
OTHER;
|
||||
static NameType fromName(String name) {
|
||||
if (name.equals(ScriptMethodInfoSerializingTests.EXECUTE)) {
|
||||
return EXECUTE;
|
||||
} else if (name.startsWith(GET_PREFIX)) {
|
||||
return GETTER;
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScriptMethodInfo doParseInstance(XContentParser parser) throws IOException {
|
||||
return ScriptMethodInfo.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ScriptMethodInfo createTestInstance() {
|
||||
return randomInstance(NameType.OTHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Writeable.Reader<ScriptMethodInfo> instanceReader() { return ScriptMethodInfo::new; }
|
||||
|
||||
@Override
|
||||
protected ScriptMethodInfo mutateInstance(ScriptMethodInfo instance) throws IOException {
|
||||
return mutate(instance);
|
||||
}
|
||||
|
||||
static ScriptMethodInfo randomInstance(NameType type) {
|
||||
switch (type) {
|
||||
case EXECUTE:
|
||||
return new ScriptMethodInfo(
|
||||
EXECUTE,
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
ScriptParameterInfoSerializingTests.randomInstances()
|
||||
);
|
||||
case GETTER:
|
||||
return new ScriptMethodInfo(
|
||||
GET_PREFIX + randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
Collections.unmodifiableList(new ArrayList<>())
|
||||
);
|
||||
default:
|
||||
return new ScriptMethodInfo(
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
ScriptParameterInfoSerializingTests.randomInstances()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static ScriptMethodInfo mutate(ScriptMethodInfo instance) {
|
||||
switch (NameType.fromName(instance.name)) {
|
||||
case EXECUTE:
|
||||
if (randomBoolean()) {
|
||||
return new ScriptMethodInfo(
|
||||
instance.name,
|
||||
instance.returnType + randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
instance.parameters
|
||||
);
|
||||
}
|
||||
return new ScriptMethodInfo(
|
||||
instance.name,
|
||||
instance.returnType,
|
||||
ScriptParameterInfoSerializingTests.mutateOne(instance.parameters)
|
||||
);
|
||||
case GETTER:
|
||||
return new ScriptMethodInfo(
|
||||
instance.name,
|
||||
instance.returnType + randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
instance.parameters
|
||||
);
|
||||
default:
|
||||
switch (randomIntBetween(0, 2)) {
|
||||
case 0:
|
||||
return new ScriptMethodInfo(
|
||||
instance.name + randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
instance.returnType,
|
||||
instance.parameters
|
||||
);
|
||||
case 1:
|
||||
return new ScriptMethodInfo(
|
||||
instance.name,
|
||||
instance.returnType + randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
instance.parameters
|
||||
);
|
||||
default:
|
||||
return new ScriptMethodInfo(
|
||||
instance.name,
|
||||
instance.returnType,
|
||||
ScriptParameterInfoSerializingTests.mutateOne(instance.parameters)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Set<ScriptMethodInfo> mutateOneGetter(Set<ScriptMethodInfo> instances) {
|
||||
if (instances.size() == 0) {
|
||||
return Collections.unmodifiableSet(Collections.singleton(randomInstance(NameType.GETTER)));
|
||||
}
|
||||
ArrayList<ScriptMethodInfo> mutated = new ArrayList<>(instances);
|
||||
int mutateIndex = randomIntBetween(0, instances.size() - 1);
|
||||
mutated.set(mutateIndex, mutate(mutated.get(mutateIndex)));
|
||||
return Collections.unmodifiableSet(new HashSet<>(mutated));
|
||||
}
|
||||
|
||||
static Set<ScriptMethodInfo> randomGetterInstances() {
|
||||
Set<String> suffixes = new HashSet<>();
|
||||
int numGetters = randomIntBetween(0, MAX_LENGTH);
|
||||
Set<ScriptMethodInfo> getters = new HashSet<>(numGetters);
|
||||
for (int i = 0; i < numGetters; i++) {
|
||||
String suffix = randomValueOtherThanMany(suffixes::contains, () -> randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH));
|
||||
suffixes.add(suffix);
|
||||
getters.add(new ScriptMethodInfo(
|
||||
GET_PREFIX + suffix,
|
||||
randomAlphaOfLengthBetween(MIN_LENGTH, MAX_LENGTH),
|
||||
Collections.unmodifiableList(new ArrayList<>())
|
||||
));
|
||||
}
|
||||
return Collections.unmodifiableSet(getters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.action.admin.cluster.storedscripts;
|
||||
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.ScriptContextInfo.ScriptMethodInfo.ParameterInfo;
|
||||
import org.elasticsearch.test.AbstractSerializingTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScriptParameterInfoSerializingTests extends AbstractSerializingTestCase<ParameterInfo> {
|
||||
private static int minLength = 1;
|
||||
private static int maxLength = 8;
|
||||
private static String baseType = "type-";
|
||||
private static String baseName = "name-";
|
||||
|
||||
@Override
|
||||
protected ParameterInfo doParseInstance(XContentParser parser) throws IOException {
|
||||
return ParameterInfo.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ParameterInfo createTestInstance() {
|
||||
return randomInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Writeable.Reader<ParameterInfo> instanceReader() {
|
||||
return ParameterInfo::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ParameterInfo mutateInstance(ParameterInfo instance) throws IOException {
|
||||
return mutate(instance);
|
||||
}
|
||||
|
||||
private static ParameterInfo mutate(ParameterInfo instance) {
|
||||
if (randomBoolean()) {
|
||||
return new ParameterInfo(instance.type + randomAlphaOfLengthBetween(minLength, maxLength), instance.name);
|
||||
}
|
||||
return new ParameterInfo(instance.type, instance.name + randomAlphaOfLengthBetween(minLength, maxLength));
|
||||
}
|
||||
|
||||
static List<ParameterInfo> mutateOne(List<ParameterInfo> instances) {
|
||||
if (instances.size() == 0) {
|
||||
return Collections.singletonList(randomInstance());
|
||||
}
|
||||
ArrayList<ParameterInfo> mutated = new ArrayList<>(instances);
|
||||
int mutateIndex = randomIntBetween(0, instances.size() - 1);
|
||||
mutated.set(mutateIndex, mutate(instances.get(mutateIndex)));
|
||||
return Collections.unmodifiableList(mutated);
|
||||
}
|
||||
|
||||
static ParameterInfo randomInstance() {
|
||||
return new ParameterInfo(
|
||||
baseType + randomAlphaOfLengthBetween(minLength, maxLength),
|
||||
baseName + randomAlphaOfLengthBetween(minLength, maxLength)
|
||||
);
|
||||
}
|
||||
|
||||
static List<ParameterInfo> randomInstances() {
|
||||
Set<String> suffixes = new HashSet<>();
|
||||
int size = randomIntBetween(0, maxLength);
|
||||
ArrayList<ParameterInfo> instances = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String suffix = randomValueOtherThanMany(suffixes::contains, () -> randomAlphaOfLengthBetween(minLength, maxLength));
|
||||
suffixes.add(suffix);
|
||||
instances.add(new ParameterInfo(
|
||||
baseType + randomAlphaOfLengthBetween(minLength, maxLength),
|
||||
baseName + suffix
|
||||
));
|
||||
}
|
||||
return Collections.unmodifiableList(instances);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.script.ScriptContextInfo.ScriptMethodInfo;
|
||||
import org.elasticsearch.script.ScriptContextInfo.ScriptMethodInfo.ParameterInfo;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ScriptContextInfoTests extends ESTestCase {
|
||||
public interface MinimalContext {
|
||||
void execute();
|
||||
}
|
||||
|
||||
public void testMinimalContext() {
|
||||
String name = "minimal_context";
|
||||
ScriptContextInfo info = new ScriptContextInfo(name, MinimalContext.class);
|
||||
assertEquals(name, info.name);
|
||||
assertEquals("execute", info.execute.name);
|
||||
assertEquals("void", info.execute.returnType);
|
||||
assertEquals(0, info.execute.parameters.size());
|
||||
assertEquals(0, info.getters.size());
|
||||
}
|
||||
|
||||
public static class PrimitiveContext {
|
||||
public int execute(boolean foo, long bar, short baz, float qux) {return 0;}
|
||||
public static final String[] PARAMETERS = {"foo", "bar", "baz", "qux"};
|
||||
public byte getByte() {return 0x00;}
|
||||
public char getChar() {return 'a';}
|
||||
}
|
||||
|
||||
public void testPrimitiveContext() {
|
||||
String name = "primitive_context";
|
||||
ScriptContextInfo info = new ScriptContextInfo(name, PrimitiveContext.class);
|
||||
assertEquals(name, info.name);
|
||||
assertEquals("execute", info.execute.name);
|
||||
assertEquals("int", info.execute.returnType);
|
||||
assertEquals(4, info.execute.parameters.size());
|
||||
List<Tuple<String, String>> eparams = new ArrayList<>();
|
||||
eparams.add(new Tuple<>("boolean", "foo"));
|
||||
eparams.add(new Tuple<>("long", "bar"));
|
||||
eparams.add(new Tuple<>("short", "baz"));
|
||||
eparams.add(new Tuple<>("float", "qux"));
|
||||
for (int i=0; i < info.execute.parameters.size(); i++) {
|
||||
assertEquals(eparams.get(i).v1(), info.execute.parameters.get(i).type);
|
||||
assertEquals(eparams.get(i).v2(), info.execute.parameters.get(i).name);
|
||||
}
|
||||
assertEquals(2, info.getters.size());
|
||||
HashMap<String,String> getters = new HashMap<String,String>() {{
|
||||
put("getByte", "byte");
|
||||
put("getChar", "char");
|
||||
}};
|
||||
for (ScriptContextInfo.ScriptMethodInfo getter: info.getters) {
|
||||
assertEquals(0, getter.parameters.size());
|
||||
String returnType = getters.remove(getter.name);
|
||||
assertNotNull(returnType);
|
||||
assertEquals(returnType, getter.returnType);
|
||||
}
|
||||
assertEquals(0, getters.size());
|
||||
}
|
||||
|
||||
|
||||
public static class CustomType0 {}
|
||||
public static class CustomType1 {}
|
||||
public static class CustomType2 {}
|
||||
|
||||
public static class CustomTypeContext {
|
||||
public CustomType0 execute(CustomType1 custom1, CustomType2 custom2) {return new CustomType0();}
|
||||
public static final String[] PARAMETERS = {"custom1", "custom2"};
|
||||
public CustomType1 getCustom1() {return new CustomType1();}
|
||||
public CustomType2 getCustom2() {return new CustomType2();}
|
||||
}
|
||||
|
||||
public void testCustomTypeContext() {
|
||||
String ct = "org.elasticsearch.script.ScriptContextInfoTests$CustomType";
|
||||
String ct0 = ct + 0;
|
||||
String ct1 = ct + 1;
|
||||
String ct2 = ct + 2;
|
||||
String name = "custom_type_context";
|
||||
ScriptContextInfo info = new ScriptContextInfo(name, CustomTypeContext.class);
|
||||
assertEquals(name, info.name);
|
||||
assertEquals("execute", info.execute.name);
|
||||
assertEquals(ct0, info.execute.returnType);
|
||||
assertEquals(2, info.execute.parameters.size());
|
||||
List<Tuple<String, String>> eparams = new ArrayList<>();
|
||||
eparams.add(new Tuple<>(ct1, "custom1"));
|
||||
eparams.add(new Tuple<>(ct2, "custom2"));
|
||||
for (int i=0; i < info.execute.parameters.size(); i++) {
|
||||
assertEquals(eparams.get(i).v1(), info.execute.parameters.get(i).type);
|
||||
assertEquals(eparams.get(i).v2(), info.execute.parameters.get(i).name);
|
||||
}
|
||||
assertEquals(2, info.getters.size());
|
||||
HashMap<String,String> getters = new HashMap<String,String>(){{
|
||||
put("getCustom1",ct1);
|
||||
put("getCustom2",ct2);
|
||||
}};
|
||||
for (ScriptContextInfo.ScriptMethodInfo getter: info.getters) {
|
||||
assertEquals(0, getter.parameters.size());
|
||||
String returnType = getters.remove(getter.name);
|
||||
assertNotNull(returnType);
|
||||
assertEquals(returnType, getter.returnType);
|
||||
}
|
||||
assertEquals(0, getters.size());
|
||||
|
||||
HashMap<String,String> methods = new HashMap<String,String>(){{
|
||||
put("getCustom1",ct1);
|
||||
put("getCustom2",ct2);
|
||||
put("execute",ct0);
|
||||
}};
|
||||
for (ScriptContextInfo.ScriptMethodInfo method: info.methods()) {
|
||||
String returnType = methods.remove(method.name);
|
||||
assertNotNull(returnType);
|
||||
assertEquals(returnType, method.returnType);
|
||||
}
|
||||
assertEquals(0, methods.size());
|
||||
}
|
||||
|
||||
public static class TwoExecute {
|
||||
public void execute(int foo) {}
|
||||
public boolean execute(boolean foo) {return foo;}
|
||||
public static final String[] PARAMETERS = {"foo"};
|
||||
}
|
||||
|
||||
public void testTwoExecute() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("two_execute", TwoExecute.class));
|
||||
assertEquals("Cannot have multiple [execute] methods on class [" + TwoExecute.class.getName() + "]", e.getMessage());
|
||||
}
|
||||
|
||||
public static class NoExecute {}
|
||||
|
||||
public void testNoExecute() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("no_execute", NoExecute.class));
|
||||
assertEquals("Could not find required method [execute] on class [" + NoExecute.class.getName() + "]", e.getMessage());
|
||||
}
|
||||
|
||||
public static class NoParametersField {
|
||||
public void execute(int foo) {}
|
||||
}
|
||||
|
||||
public void testNoParametersField() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("no_parameters_field", NoParametersField.class));
|
||||
assertEquals("Could not find field [PARAMETERS] on instance class [" + NoParametersField.class.getName() +
|
||||
"] but method [execute] has [1] parameters", e.getMessage());
|
||||
}
|
||||
|
||||
public static class BadParametersFieldType {
|
||||
public void execute(int foo) {}
|
||||
public static final int[] PARAMETERS = {1};
|
||||
}
|
||||
|
||||
public void testBadParametersFieldType() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("bad_parameters_field_type", BadParametersFieldType.class));
|
||||
assertEquals("Expected a constant [String[] PARAMETERS] on instance class [" + BadParametersFieldType.class.getName() +
|
||||
"] for method [execute] with [1] parameters, found [int[]]", e.getMessage());
|
||||
}
|
||||
|
||||
public static class WrongNumberOfParameters {
|
||||
public void execute(int foo) {}
|
||||
public static final String[] PARAMETERS = {"foo", "bar"};
|
||||
}
|
||||
|
||||
public void testWrongNumberOfParameters() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("wrong_number_of_parameters", WrongNumberOfParameters.class));
|
||||
assertEquals("Expected argument names [2] to have the same arity [1] for method [execute] of class ["
|
||||
+ WrongNumberOfParameters.class.getName() + "]", e.getMessage());
|
||||
}
|
||||
|
||||
public interface Default {
|
||||
default int getDefault() {return 1;}
|
||||
boolean getNonDefault1();
|
||||
}
|
||||
|
||||
public static class GetterConditional implements Default {
|
||||
public void execute() {}
|
||||
public boolean getNonDefault1() {return true;}
|
||||
public float getNonDefault2() {return 0.1f;}
|
||||
public static long getStatic() {return 2L;}
|
||||
public char getChar(char ch) { return ch;}
|
||||
}
|
||||
|
||||
public void testGetterConditional() {
|
||||
Set<ScriptMethodInfo> getters =
|
||||
new ScriptContextInfo("getter_conditional", GetterConditional.class).getters;
|
||||
assertEquals(2, getters.size());
|
||||
HashMap<String,String> methods = new HashMap<String,String>(){{
|
||||
put("getNonDefault1","boolean");
|
||||
put("getNonDefault2","float");
|
||||
}};
|
||||
for (ScriptContextInfo.ScriptMethodInfo method: getters) {
|
||||
String returnType = methods.remove(method.name);
|
||||
assertNotNull(returnType);
|
||||
assertEquals(returnType, method.returnType);
|
||||
}
|
||||
assertEquals(0, methods.size());
|
||||
}
|
||||
|
||||
public void testParameterInfoParser() throws IOException {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
|
||||
XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(
|
||||
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
|
||||
new BytesArray("{\"type\":\"foo\", \"name\": \"bar\"}").streamInput());
|
||||
ScriptContextInfo.ScriptMethodInfo.ParameterInfo info = ScriptContextInfo.ScriptMethodInfo.ParameterInfo.fromXContent(parser);
|
||||
assertEquals(new ScriptContextInfo.ScriptMethodInfo.ParameterInfo("foo", "bar"), info);
|
||||
}
|
||||
|
||||
public void testScriptMethodInfoParser() throws IOException {
|
||||
String json = "{\"name\": \"fooFunc\", \"return_type\": \"int\", \"params\": [{\"type\": \"int\", \"name\": \"fooParam\"}, " +
|
||||
"{\"type\": \"java.util.Map\", \"name\": \"barParam\"}]}";
|
||||
XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(
|
||||
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
|
||||
new BytesArray(json).streamInput());
|
||||
ScriptContextInfo.ScriptMethodInfo info = ScriptContextInfo.ScriptMethodInfo.fromXContent(parser);
|
||||
assertEquals(new ScriptContextInfo.ScriptMethodInfo("fooFunc", "int", new ArrayList<>(
|
||||
Arrays.asList(new ScriptContextInfo.ScriptMethodInfo.ParameterInfo("int", "fooParam"),
|
||||
new ScriptContextInfo.ScriptMethodInfo.ParameterInfo("java.util.Map", "barParam"))
|
||||
)), info);
|
||||
}
|
||||
|
||||
public void testScriptContextInfoParser() throws IOException {
|
||||
String json = "{" +
|
||||
" \"name\": \"similarity\"," +
|
||||
" \"methods\": [" +
|
||||
" {" +
|
||||
" \"name\": \"execute\"," +
|
||||
" \"return_type\": \"double\"," +
|
||||
" \"params\": [" +
|
||||
" {" +
|
||||
" \"type\": \"double\"," +
|
||||
" \"name\": \"weight\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"type\": \"org.elasticsearch.index.similarity.ScriptedSimilarity$Query\"," +
|
||||
" \"name\": \"query\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"type\": \"org.elasticsearch.index.similarity.ScriptedSimilarity$Field\"," +
|
||||
" \"name\": \"field\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"type\": \"org.elasticsearch.index.similarity.ScriptedSimilarity$Term\"," +
|
||||
" \"name\": \"term\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"type\": \"org.elasticsearch.index.similarity.ScriptedSimilarity$Doc\"," +
|
||||
" \"name\": \"doc\"" +
|
||||
" }" +
|
||||
" ]" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"name\": \"getParams\"," +
|
||||
" \"return_type\": \"java.util.Map\"," +
|
||||
" \"params\": []" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"name\": \"getDoc\"," +
|
||||
" \"return_type\": \"java.util.Map\"," +
|
||||
" \"params\": []" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"name\": \"get_score\"," +
|
||||
" \"return_type\": \"double\"," +
|
||||
" \"params\": []" +
|
||||
" }" +
|
||||
" ]" +
|
||||
"}";
|
||||
XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(
|
||||
NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION,
|
||||
new BytesArray(json).streamInput());
|
||||
ScriptContextInfo parsed = ScriptContextInfo.fromXContent(parser);
|
||||
ScriptContextInfo expected = new ScriptContextInfo(
|
||||
"similarity",
|
||||
new ScriptMethodInfo(
|
||||
"execute",
|
||||
"double",
|
||||
Collections.unmodifiableList(Arrays.asList(
|
||||
new ParameterInfo("double", "weight"),
|
||||
new ParameterInfo("org.elasticsearch.index.similarity.ScriptedSimilarity$Query", "query"),
|
||||
new ParameterInfo("org.elasticsearch.index.similarity.ScriptedSimilarity$Field", "field"),
|
||||
new ParameterInfo("org.elasticsearch.index.similarity.ScriptedSimilarity$Term", "term"),
|
||||
new ParameterInfo("org.elasticsearch.index.similarity.ScriptedSimilarity$Doc", "doc")
|
||||
)
|
||||
)),
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
new ScriptMethodInfo("getParams", "java.util.Map", new ArrayList<>()),
|
||||
new ScriptMethodInfo("getDoc", "java.util.Map", new ArrayList<>()),
|
||||
new ScriptMethodInfo("get_score", "double", new ArrayList<>())
|
||||
)))
|
||||
);
|
||||
assertEquals(expected, parsed);
|
||||
}
|
||||
|
||||
public void testIgnoreOtherMethodsInListConstructor() {
|
||||
ScriptContextInfo constructed = new ScriptContextInfo("otherNames", Collections.unmodifiableList(Arrays.asList(
|
||||
new ScriptMethodInfo("execute", "double", Collections.emptyList()),
|
||||
new ScriptMethodInfo("otherName", "bool", Collections.emptyList())
|
||||
)));
|
||||
ScriptContextInfo expected = new ScriptContextInfo("otherNames",
|
||||
new ScriptMethodInfo("execute", "double", Collections.emptyList()),
|
||||
Collections.emptySet()
|
||||
);
|
||||
assertEquals(expected, constructed);
|
||||
}
|
||||
|
||||
public void testNoExecuteInListConstructor() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("noExecute", Collections.unmodifiableList(Arrays.asList(
|
||||
new ScriptMethodInfo("getSomeOther", "int", Collections.emptyList()),
|
||||
new ScriptMethodInfo("getSome", "bool", Collections.emptyList())
|
||||
))));
|
||||
assertEquals("Could not find required method [execute] in [noExecute], found [getSome, getSomeOther]", e.getMessage());
|
||||
}
|
||||
|
||||
public void testMultipleExecuteInListConstructor() {
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
|
||||
new ScriptContextInfo("multiexecute", Collections.unmodifiableList(Arrays.asList(
|
||||
new ScriptMethodInfo("execute", "double", Collections.emptyList()),
|
||||
new ScriptMethodInfo("execute", "double", Collections.unmodifiableList(Arrays.asList(
|
||||
new ParameterInfo("double", "weight")
|
||||
)))))));
|
||||
assertEquals("Cannot have multiple [execute] methods in [multiexecute], found [2]", e.getMessage());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue