Scripting: fill in get contexts REST API (#48319) (#48602)

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:
Stuart Tettemer 2019-10-29 14:41:15 -06:00 committed by GitHub
parent 50d7424e7d
commit 55d00cf2b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1179 additions and 79 deletions

View File

@ -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" }

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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() {

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}