Add a Painless Context REST API (#39382)

This PR adds an internal REST API for querying context information about 
Painless whitelists.

Commands include the following:
GET /_scripts/painless/_context -- retrieves a list of contexts
GET /_scripts/painless/_context?context=%name% retrieves all available 
information about the API for this specific context
This commit is contained in:
Jack Conradson 2019-03-14 09:18:57 -07:00
parent ae0ff05c90
commit b57af6c401
16 changed files with 1519 additions and 7 deletions

View File

@ -713,6 +713,7 @@ public class RestHighLevelClientTests extends ESTestCase {
"nodes.hot_threads",
"nodes.usage",
"nodes.reload_secure_settings",
"scripts_painless_context",
"search_shards",
};
List<String> booleanReturnMethods = Arrays.asList(

View File

@ -20,15 +20,18 @@
package org.elasticsearch.painless;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.painless.action.PainlessContextAction;
import org.elasticsearch.painless.spi.PainlessExtension;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.spi.WhitelistLoader;
@ -81,6 +84,8 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
whitelists = map;
}
private final SetOnce<PainlessScriptEngine> painlessScriptEngine = new SetOnce<>();
@Override
public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
Map<ScriptContext<?>, List<Whitelist>> contextsWithWhitelists = new HashMap<>();
@ -92,7 +97,13 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
}
contextsWithWhitelists.put(context, contextWhitelists);
}
return new PainlessScriptEngine(settings, contextsWithWhitelists);
painlessScriptEngine.set(new PainlessScriptEngine(settings, contextsWithWhitelists));
return painlessScriptEngine.get();
}
@Override
public Collection<Module> createGuiceModules() {
return Collections.singleton(b -> b.bind(PainlessScriptEngine.class).toInstance(painlessScriptEngine.get()));
}
@Override
@ -118,9 +129,10 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
return Collections.singletonList(
new ActionHandler<>(PainlessExecuteAction.INSTANCE, PainlessExecuteAction.TransportAction.class)
);
List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> actions = new ArrayList<>();
actions.add(new ActionHandler<>(PainlessExecuteAction.INSTANCE, PainlessExecuteAction.TransportAction.class));
actions.add(new ActionHandler<>(PainlessContextAction.INSTANCE, PainlessContextAction.TransportAction.class));
return actions;
}
@Override
@ -128,6 +140,9 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<DiscoveryNodes> nodesInCluster) {
return Collections.singletonList(new PainlessExecuteAction.RestAction(settings, restController));
List<RestHandler> handlers = new ArrayList<>();
handlers.add(new PainlessExecuteAction.RestAction(settings, restController));
handlers.add(new PainlessContextAction.RestAction(settings, restController));
return handlers;
}
}

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.painless.Compiler.Loader;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessLookupBuilder;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.script.ScriptContext;
@ -82,6 +83,7 @@ public final class PainlessScriptEngine implements ScriptEngine {
private final CompilerSettings defaultCompilerSettings = new CompilerSettings();
private final Map<ScriptContext<?>, Compiler> contextsToCompilers;
private final Map<ScriptContext<?>, PainlessLookup> contextsToLookups;
/**
* Constructor.
@ -91,14 +93,22 @@ public final class PainlessScriptEngine implements ScriptEngine {
defaultCompilerSettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(settings));
Map<ScriptContext<?>, Compiler> contextsToCompilers = new HashMap<>();
Map<ScriptContext<?>, PainlessLookup> contextsToLookups = new HashMap<>();
for (Map.Entry<ScriptContext<?>, List<Whitelist>> entry : contexts.entrySet()) {
ScriptContext<?> context = entry.getKey();
contextsToCompilers.put(context, new Compiler(context.instanceClazz, context.factoryClazz, context.statefulFactoryClazz,
PainlessLookupBuilder.buildFromWhitelists(entry.getValue())));
PainlessLookup lookup = PainlessLookupBuilder.buildFromWhitelists(entry.getValue());
contextsToCompilers.put(context,
new Compiler(context.instanceClazz, context.factoryClazz, context.statefulFactoryClazz, lookup));
contextsToLookups.put(context, lookup);
}
this.contextsToCompilers = Collections.unmodifiableMap(contextsToCompilers);
this.contextsToLookups = Collections.unmodifiableMap(contextsToLookups);
}
public Map<ScriptContext<?>, PainlessLookup> getContextsToLookups() {
return contextsToLookups;
}
/**

View File

@ -0,0 +1,236 @@
/*
* 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.painless.action;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.inject.Inject;
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.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.painless.PainlessScriptEngine;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.elasticsearch.rest.RestRequest.Method.GET;
/**
* Internal REST API for querying context information about Painless whitelists.
* Commands include the following:
* <ul>
* <li> GET /_scripts/painless/_context -- retrieves a list of contexts </li>
* <li> GET /_scripts/painless/_context?context=%name% --
* retrieves all available information about the API for this specific context</li>
* </ul>
*/
public class PainlessContextAction extends Action<PainlessContextAction.Response> {
public static final PainlessContextAction INSTANCE = new PainlessContextAction();
private static final String NAME = "cluster:admin/scripts/painless/context";
private static final String SCRIPT_CONTEXT_NAME_PARAM = "context";
private PainlessContextAction() {
super(NAME);
}
@Override
public Response newResponse() {
throw new UnsupportedOperationException();
}
@Override
public Writeable.Reader<Response> getResponseReader() {
return Response::new;
}
public static class Request extends ActionRequest {
private String scriptContextName;
public Request() {
scriptContextName = null;
}
public Request(StreamInput in) throws IOException {
super(in);
scriptContextName = in.readString();
}
public void setScriptContextName(String scriptContextName) {
this.scriptContextName = scriptContextName;
}
public String getScriptContextName() {
return scriptContextName;
}
@Override
public ActionRequestValidationException validate() {
return null;
}
@Override
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(scriptContextName);
}
}
public static class Response extends ActionResponse implements ToXContentObject {
public static final ParseField CONTEXTS = new ParseField("contexts");
private final List<String> scriptContextNames;
private final PainlessContextInfo painlessContextInfo;
public Response(List<String> scriptContextNames, PainlessContextInfo painlessContextInfo) {
Objects.requireNonNull(scriptContextNames);
scriptContextNames = new ArrayList<>(scriptContextNames);
scriptContextNames.sort(String::compareTo);
this.scriptContextNames = Collections.unmodifiableList(scriptContextNames);
this.painlessContextInfo = painlessContextInfo;
}
public Response(StreamInput in) throws IOException {
super(in);
scriptContextNames = in.readStringList();
painlessContextInfo = in.readOptionalWriteable(PainlessContextInfo::new);
}
@Override
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringCollection(scriptContextNames);
out.writeOptionalWriteable(painlessContextInfo);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
if (painlessContextInfo == null) {
builder.startObject();
builder.field(CONTEXTS.getPreferredName(), scriptContextNames);
builder.endObject();
} else {
painlessContextInfo.toXContent(builder, params);
}
return builder;
}
}
public static class TransportAction extends HandledTransportAction<Request, Response> {
private final PainlessScriptEngine painlessScriptEngine;
@Inject
public TransportAction(TransportService transportService, ActionFilters actionFilters, PainlessScriptEngine painlessScriptEngine) {
super(NAME, transportService, actionFilters, (Writeable.Reader<Request>)Request::new);
this.painlessScriptEngine = painlessScriptEngine;
}
@Override
protected void doExecute(Task task, Request request, ActionListener<Response> listener) {
List<String> scriptContextNames;
PainlessContextInfo painlessContextInfo;
if (request.scriptContextName == null) {
scriptContextNames =
painlessScriptEngine.getContextsToLookups().keySet().stream().map(v -> v.name).collect(Collectors.toList());
painlessContextInfo = null;
} else {
ScriptContext<?> scriptContext = null;
PainlessLookup painlessLookup = null;
for (Map.Entry<ScriptContext<?>, PainlessLookup> contextLookupEntry :
painlessScriptEngine.getContextsToLookups().entrySet()) {
if (contextLookupEntry.getKey().name.equals(request.getScriptContextName())) {
scriptContext = contextLookupEntry.getKey();
painlessLookup = contextLookupEntry.getValue();
break;
}
}
if (scriptContext == null || painlessLookup == null) {
throw new IllegalArgumentException("script context [" + request.getScriptContextName() + "] not found");
}
scriptContextNames = Collections.emptyList();
painlessContextInfo = new PainlessContextInfo(scriptContext, painlessLookup);
}
listener.onResponse(new Response(scriptContextNames, painlessContextInfo));
}
}
public static class RestAction extends BaseRestHandler {
public RestAction(Settings settings, RestController controller) {
super(settings);
controller.registerHandler(GET, "/_scripts/painless/_context", this);
}
@Override
public String getName() {
return "_scripts_painless_context";
}
@Override
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
Request request = new Request();
request.setScriptContextName(restRequest.param(SCRIPT_CONTEXT_NAME_PARAM));
return channel -> client.executeLocally(INSTANCE, request, new RestToXContentListener<>(channel));
}
}
}

View File

@ -0,0 +1,147 @@
/*
* 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.painless.action;
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.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.painless.lookup.PainlessClassBinding;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextClassBindingInfo implements Writeable, ToXContentObject {
public static final ParseField DECLARING = new ParseField("declaring");
public static final ParseField NAME = new ParseField("name");
public static final ParseField RTN = new ParseField("return");
public static final ParseField READ_ONLY = new ParseField("read_only");
public static final ParseField PARAMETERS = new ParseField("parameters");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextClassBindingInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextClassBindingInfo.class.getCanonicalName(),
(v) ->
new PainlessContextClassBindingInfo(
(String)v[0],
(String)v[1],
(String)v[2],
(int)v[3],
(List<String>)v[4]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), DECLARING);
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareString(ConstructingObjectParser.constructorArg(), RTN);
PARSER.declareInt(ConstructingObjectParser.constructorArg(), READ_ONLY);
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PARAMETERS);
}
private final String declaring;
private final String name;
private final String rtn;
private final int readOnly;
private final List<String> parameters;
public PainlessContextClassBindingInfo(PainlessClassBinding painlessClassBinding) {
this(
painlessClassBinding.javaMethod.getDeclaringClass().getName(),
painlessClassBinding.javaMethod.getName(),
painlessClassBinding.returnType.getName(),
painlessClassBinding.javaConstructor.getParameterCount(),
painlessClassBinding.typeParameters.stream().map(Class::getName).collect(Collectors.toList())
);
}
public PainlessContextClassBindingInfo(String declaring, String name, String rtn, int readOnly, List<String> parameters) {
this.declaring = Objects.requireNonNull(declaring);
this.name = Objects.requireNonNull(name);
this.rtn = Objects.requireNonNull(rtn);
this.readOnly = readOnly;
this.parameters = Collections.unmodifiableList(Objects.requireNonNull(parameters));
}
public PainlessContextClassBindingInfo(StreamInput in) throws IOException {
declaring = in.readString();
name = in.readString();
rtn = in.readString();
readOnly = in.readInt();
parameters = Collections.unmodifiableList(in.readStringList());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(declaring);
out.writeString(name);
out.writeString(rtn);
out.writeInt(readOnly);
out.writeStringCollection(parameters);
}
public static PainlessContextClassBindingInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(DECLARING.getPreferredName(), declaring);
builder.field(NAME.getPreferredName(), name);
builder.field(RTN.getPreferredName(), rtn);
builder.field(READ_ONLY.getPreferredName(), readOnly);
builder.field(PARAMETERS.getPreferredName(), parameters);
builder.endObject();
return builder;
}
public String getSortValue() {
return PainlessLookupUtility.buildPainlessMethodKey(name, parameters.size());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextClassBindingInfo that = (PainlessContextClassBindingInfo) o;
return readOnly == that.readOnly &&
Objects.equals(declaring, that.declaring) &&
Objects.equals(name, that.name) &&
Objects.equals(rtn, that.rtn) &&
Objects.equals(parameters, that.parameters);
}
@Override
public int hashCode() {
return Objects.hash(declaring, name, rtn, readOnly, parameters);
}
}

View File

@ -0,0 +1,186 @@
/*
* 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.painless.action;
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 org.elasticsearch.painless.lookup.PainlessClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextClassInfo implements Writeable, ToXContentObject {
public static final ParseField NAME = new ParseField("name");
public static final ParseField IMPORTED = new ParseField("imported");
public static final ParseField CONSTRUCTORS = new ParseField("constructors");
public static final ParseField STATIC_METHODS = new ParseField("static_methods");
public static final ParseField METHODS = new ParseField("methods");
public static final ParseField STATIC_FIELDS = new ParseField("static_fields");
public static final ParseField FIELDS = new ParseField("fields");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextClassInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextClassInfo.class.getCanonicalName(),
(v) ->
new PainlessContextClassInfo(
(String)v[0],
(boolean)v[1],
(List<PainlessContextConstructorInfo>)v[2],
(List<PainlessContextMethodInfo>)v[3],
(List<PainlessContextMethodInfo>)v[4],
(List<PainlessContextFieldInfo>)v[5],
(List<PainlessContextFieldInfo>)v[6]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), IMPORTED);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextConstructorInfo.fromXContent(p), CONSTRUCTORS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextMethodInfo.fromXContent(p), STATIC_METHODS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextMethodInfo.fromXContent(p), METHODS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextFieldInfo.fromXContent(p), STATIC_FIELDS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextFieldInfo.fromXContent(p), FIELDS);
}
private final String name;
private final boolean imported;
private final List<PainlessContextConstructorInfo> constructors;
private final List<PainlessContextMethodInfo> staticMethods;
private final List<PainlessContextMethodInfo> methods;
private final List<PainlessContextFieldInfo> staticFields;
private final List<PainlessContextFieldInfo> fields;
public PainlessContextClassInfo(Class<?> javaClass, boolean imported, PainlessClass painlessClass) {
this(
javaClass.getName(),
imported,
painlessClass.constructors.values().stream().map(PainlessContextConstructorInfo::new).collect(Collectors.toList()),
painlessClass.staticMethods.values().stream().map(PainlessContextMethodInfo::new).collect(Collectors.toList()),
painlessClass.methods.values().stream().map(PainlessContextMethodInfo::new).collect(Collectors.toList()),
painlessClass.staticFields.values().stream().map(PainlessContextFieldInfo::new).collect(Collectors.toList()),
painlessClass.fields.values().stream().map(PainlessContextFieldInfo::new).collect(Collectors.toList())
);
}
public PainlessContextClassInfo(String name, boolean imported,
List<PainlessContextConstructorInfo> constructors,
List<PainlessContextMethodInfo> staticMethods, List<PainlessContextMethodInfo> methods,
List<PainlessContextFieldInfo> staticFields, List<PainlessContextFieldInfo> fields) {
this.name = Objects.requireNonNull(name);
this.imported = imported;
constructors = new ArrayList<>(Objects.requireNonNull(constructors));
constructors.sort(Comparator.comparing(PainlessContextConstructorInfo::getSortValue));
this.constructors = Collections.unmodifiableList(constructors);
staticMethods = new ArrayList<>(Objects.requireNonNull(staticMethods));
staticMethods.sort(Comparator.comparing(PainlessContextMethodInfo::getSortValue));
this.staticMethods = Collections.unmodifiableList(staticMethods);
methods = new ArrayList<>(Objects.requireNonNull(methods));
methods.sort(Comparator.comparing(PainlessContextMethodInfo::getSortValue));
this.methods = Collections.unmodifiableList(methods);
staticFields = new ArrayList<>(Objects.requireNonNull(staticFields));
staticFields.sort(Comparator.comparing(PainlessContextFieldInfo::getSortValue));
this.staticFields = Collections.unmodifiableList(staticFields);
fields = new ArrayList<>(Objects.requireNonNull(fields));
fields.sort(Comparator.comparing(PainlessContextFieldInfo::getSortValue));
this.fields = Collections.unmodifiableList(fields);
}
public PainlessContextClassInfo(StreamInput in) throws IOException {
name = in.readString();
imported = in.readBoolean();
constructors = Collections.unmodifiableList(in.readList(PainlessContextConstructorInfo::new));
staticMethods = Collections.unmodifiableList(in.readList(PainlessContextMethodInfo::new));
methods = Collections.unmodifiableList(in.readList(PainlessContextMethodInfo::new));
staticFields = Collections.unmodifiableList(in.readList(PainlessContextFieldInfo::new));
fields = Collections.unmodifiableList(in.readList(PainlessContextFieldInfo::new));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
out.writeBoolean(imported);
out.writeList(constructors);
out.writeList(staticMethods);
out.writeList(methods);
out.writeList(staticFields);
out.writeList(fields);
}
public static PainlessContextClassInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(NAME.getPreferredName(), name);
builder.field(IMPORTED.getPreferredName(), imported);
builder.field(CONSTRUCTORS.getPreferredName(), constructors);
builder.field(STATIC_METHODS.getPreferredName(), staticMethods);
builder.field(METHODS.getPreferredName(), methods);
builder.field(STATIC_FIELDS.getPreferredName(), staticFields);
builder.field(FIELDS.getPreferredName(), fields);
builder.endObject();
return builder;
}
public String getSortValue() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextClassInfo that = (PainlessContextClassInfo) o;
return imported == that.imported &&
Objects.equals(name, that.name) &&
Objects.equals(constructors, that.constructors) &&
Objects.equals(staticMethods, that.staticMethods) &&
Objects.equals(methods, that.methods) &&
Objects.equals(staticFields, that.staticFields) &&
Objects.equals(fields, that.fields);
}
@Override
public int hashCode() {
return Objects.hash(name, imported, constructors, staticMethods, methods, staticFields, fields);
}
}

View File

@ -0,0 +1,117 @@
/*
* 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.painless.action;
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.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.painless.lookup.PainlessConstructor;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextConstructorInfo implements Writeable, ToXContentObject {
public static final ParseField DECLARING = new ParseField("declaring");
public static final ParseField PARAMETERS = new ParseField("parameters");
private final String declaring;
private final List<String> parameters;
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextConstructorInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextConstructorInfo.class.getCanonicalName(),
(v) ->
new PainlessContextConstructorInfo(
(String)v[0],
(List<String>)v[1]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), DECLARING);
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PARAMETERS);
}
public PainlessContextConstructorInfo(PainlessConstructor painlessConstructor) {
this (
painlessConstructor.javaConstructor.getDeclaringClass().getName(),
painlessConstructor.typeParameters.stream().map(Class::getName).collect(Collectors.toList())
);
}
public PainlessContextConstructorInfo(String declaring, List<String> parameters) {
this.declaring = Objects.requireNonNull(declaring);
this.parameters = Collections.unmodifiableList(Objects.requireNonNull(parameters));
}
public PainlessContextConstructorInfo(StreamInput in) throws IOException {
declaring = in.readString();
parameters = Collections.unmodifiableList(in.readStringList());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(declaring);
out.writeStringCollection(parameters);
}
public static PainlessContextConstructorInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(DECLARING.getPreferredName(), declaring);
builder.field(PARAMETERS.getPreferredName(), parameters);
builder.endObject();
return builder;
}
public String getSortValue() {
return PainlessLookupUtility.buildPainlessConstructorKey(parameters.size());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextConstructorInfo that = (PainlessContextConstructorInfo) o;
return Objects.equals(declaring, that.declaring) &&
Objects.equals(parameters, that.parameters);
}
@Override
public int hashCode() {
return Objects.hash(declaring, parameters);
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.painless.action;
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 org.elasticsearch.painless.lookup.PainlessField;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import java.io.IOException;
import java.util.Objects;
public class PainlessContextFieldInfo implements Writeable, ToXContentObject {
public static final ParseField DECLARING = new ParseField("declaring");
public static final ParseField NAME = new ParseField("name");
public static final ParseField TYPE = new ParseField("type");
private static final ConstructingObjectParser<PainlessContextFieldInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextFieldInfo.class.getCanonicalName(),
(v) ->
new PainlessContextFieldInfo(
(String)v[0],
(String)v[1],
(String)v[2]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), DECLARING);
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE);
}
private final String declaring;
private final String name;
private final String type;
public PainlessContextFieldInfo(PainlessField painlessField) {
this(
painlessField.javaField.getDeclaringClass().getName(),
painlessField.javaField.getName(),
painlessField.typeParameter.getName()
);
}
public PainlessContextFieldInfo(String declaring, String name, String type) {
this.declaring = Objects.requireNonNull(declaring);
this.name = Objects.requireNonNull(name);
this.type = Objects.requireNonNull(type);
}
public PainlessContextFieldInfo(StreamInput in) throws IOException {
declaring = in.readString();
name = in.readString();
type = in.readString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(declaring);
out.writeString(name);
out.writeString(type);
}
public static PainlessContextFieldInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(DECLARING.getPreferredName(), declaring);
builder.field(NAME.getPreferredName(), name);
builder.field(TYPE.getPreferredName(), type);
builder.endObject();
return builder;
}
public String getSortValue() {
return PainlessLookupUtility.buildPainlessFieldKey(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextFieldInfo that = (PainlessContextFieldInfo) o;
return Objects.equals(declaring, that.declaring) &&
Objects.equals(name, that.name) &&
Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(declaring, name, type);
}
}

View File

@ -0,0 +1,187 @@
/*
* 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.painless.action;
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 org.elasticsearch.painless.lookup.PainlessClassBinding;
import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessMethod;
import org.elasticsearch.script.ScriptContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextInfo implements Writeable, ToXContentObject {
public static final ParseField NAME = new ParseField("name");
public static final ParseField CLASSES = new ParseField("classes");
public static final ParseField IMPORTED_METHODS = new ParseField("imported_methods");
public static final ParseField CLASS_BINDINGS = new ParseField("class_bindings");
public static final ParseField INSTANCE_BINDINGS = new ParseField("instance_bindings");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextInfo.class.getCanonicalName(),
(v) ->
new PainlessContextInfo(
(String)v[0],
(List<PainlessContextClassInfo>)v[1],
(List<PainlessContextMethodInfo>)v[2],
(List<PainlessContextClassBindingInfo>)v[3],
(List<PainlessContextInstanceBindingInfo>)v[4]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextClassInfo.fromXContent(p), CLASSES);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextMethodInfo.fromXContent(p), IMPORTED_METHODS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextClassBindingInfo.fromXContent(p), CLASS_BINDINGS);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> PainlessContextInstanceBindingInfo.fromXContent(p), INSTANCE_BINDINGS);
}
private final String name;
private final List<PainlessContextClassInfo> classes;
private final List<PainlessContextMethodInfo> importedMethods;
private final List<PainlessContextClassBindingInfo> classBindings;
private final List<PainlessContextInstanceBindingInfo> instanceBindings;
public PainlessContextInfo(ScriptContext<?> scriptContext, PainlessLookup painlessLookup) {
this(
scriptContext.name,
painlessLookup.getClasses().stream().map(
javaClass -> new PainlessContextClassInfo(
javaClass,
javaClass == painlessLookup.canonicalTypeNameToType(
javaClass.getName().substring(javaClass.getName().lastIndexOf('.') + 1).replace('$', '.')),
painlessLookup.lookupPainlessClass(javaClass))
).collect(Collectors.toList()),
painlessLookup.getImportedPainlessMethodsKeys().stream().map(importedPainlessMethodKey -> {
String[] split = importedPainlessMethodKey.split("/");
String importedPainlessMethodName = split[0];
int importedPainlessMethodArity = Integer.parseInt(split[1]);
PainlessMethod importedPainlessMethod =
painlessLookup.lookupImportedPainlessMethod(importedPainlessMethodName, importedPainlessMethodArity);
return new PainlessContextMethodInfo(importedPainlessMethod);
}).collect(Collectors.toList()),
painlessLookup.getPainlessClassBindingsKeys().stream().map(painlessClassBindingKey -> {
String[] split = painlessClassBindingKey.split("/");
String painlessClassBindingName = split[0];
int painlessClassBindingArity = Integer.parseInt(split[1]);
PainlessClassBinding painlessClassBinding =
painlessLookup.lookupPainlessClassBinding(painlessClassBindingName, painlessClassBindingArity);
return new PainlessContextClassBindingInfo(painlessClassBinding);
}).collect(Collectors.toList()),
painlessLookup.getPainlessInstanceBindingsKeys().stream().map(painlessInstanceBindingKey -> {
String[] split = painlessInstanceBindingKey.split("/");
String painlessInstanceBindingName = split[0];
int painlessInstanceBindingArity = Integer.parseInt(split[1]);
PainlessInstanceBinding painlessInstanceBinding =
painlessLookup.lookupPainlessInstanceBinding(painlessInstanceBindingName, painlessInstanceBindingArity);
return new PainlessContextInstanceBindingInfo(painlessInstanceBinding);
}).collect(Collectors.toList())
);
}
public PainlessContextInfo(String name, List<PainlessContextClassInfo> classes, List<PainlessContextMethodInfo> importedMethods,
List<PainlessContextClassBindingInfo> classBindings, List<PainlessContextInstanceBindingInfo> instanceBindings) {
this.name = Objects.requireNonNull(name);
classes = new ArrayList<>(Objects.requireNonNull(classes));
classes.sort(Comparator.comparing(PainlessContextClassInfo::getSortValue));
this.classes = Collections.unmodifiableList(classes);
importedMethods = new ArrayList<>(Objects.requireNonNull(importedMethods));
importedMethods.sort(Comparator.comparing(PainlessContextMethodInfo::getSortValue));
this.importedMethods = Collections.unmodifiableList(importedMethods);
classBindings = new ArrayList<>(Objects.requireNonNull(classBindings));
classBindings.sort(Comparator.comparing(PainlessContextClassBindingInfo::getSortValue));
this.classBindings = Collections.unmodifiableList(classBindings);
instanceBindings = new ArrayList<>(Objects.requireNonNull(instanceBindings));
instanceBindings.sort(Comparator.comparing(PainlessContextInstanceBindingInfo::getSortValue));
this.instanceBindings = Collections.unmodifiableList(instanceBindings);
}
public PainlessContextInfo(StreamInput in) throws IOException {
name = in.readString();
classes = Collections.unmodifiableList(in.readList(PainlessContextClassInfo::new));
importedMethods = Collections.unmodifiableList(in.readList(PainlessContextMethodInfo::new));
classBindings = Collections.unmodifiableList(in.readList(PainlessContextClassBindingInfo::new));
instanceBindings = Collections.unmodifiableList(in.readList(PainlessContextInstanceBindingInfo::new));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
out.writeList(classes);
out.writeList(importedMethods);
out.writeList(classBindings);
out.writeList(instanceBindings);
}
public static PainlessContextInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(NAME.getPreferredName(), name);
builder.field(CLASSES.getPreferredName(), classes);
builder.field(IMPORTED_METHODS.getPreferredName(), importedMethods);
builder.field(CLASS_BINDINGS.getPreferredName(), classBindings);
builder.field(INSTANCE_BINDINGS.getPreferredName(), instanceBindings);
builder.endObject();
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextInfo that = (PainlessContextInfo) o;
return Objects.equals(name, that.name) &&
Objects.equals(classes, that.classes) &&
Objects.equals(importedMethods, that.importedMethods) &&
Objects.equals(classBindings, that.classBindings) &&
Objects.equals(instanceBindings, that.instanceBindings);
}
@Override
public int hashCode() {
return Objects.hash(name, classes, importedMethods, classBindings, instanceBindings);
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.painless.action;
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.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.painless.lookup.PainlessInstanceBinding;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextInstanceBindingInfo implements Writeable, ToXContentObject {
public static final ParseField DECLARING = new ParseField("declaring");
public static final ParseField NAME = new ParseField("name");
public static final ParseField RTN = new ParseField("return");
public static final ParseField PARAMETERS = new ParseField("parameters");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextInstanceBindingInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextInstanceBindingInfo.class.getCanonicalName(),
(v) ->
new PainlessContextInstanceBindingInfo(
(String)v[0],
(String)v[1],
(String)v[2],
(List<String>)v[3]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), DECLARING);
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareString(ConstructingObjectParser.constructorArg(), RTN);
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PARAMETERS);
}
private final String declaring;
private final String name;
private final String rtn;
private final List<String> parameters;
public PainlessContextInstanceBindingInfo(PainlessInstanceBinding painlessInstanceBinding) {
this(
painlessInstanceBinding.javaMethod.getDeclaringClass().getName(),
painlessInstanceBinding.javaMethod.getName(),
painlessInstanceBinding.returnType.getName(),
painlessInstanceBinding.typeParameters.stream().map(Class::getName).collect(Collectors.toList())
);
}
public PainlessContextInstanceBindingInfo(String declaring, String name, String rtn, List<String> parameters) {
this.declaring = Objects.requireNonNull(declaring);
this.name = Objects.requireNonNull(name);
this.rtn = Objects.requireNonNull(rtn);
this.parameters = Collections.unmodifiableList(Objects.requireNonNull(parameters));
}
public PainlessContextInstanceBindingInfo(StreamInput in) throws IOException {
declaring = in.readString();
name = in.readString();
rtn = in.readString();
parameters = Collections.unmodifiableList(in.readStringList());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(declaring);
out.writeString(name);
out.writeString(rtn);
out.writeStringCollection(parameters);
}
public static PainlessContextInstanceBindingInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(DECLARING.getPreferredName(), declaring);
builder.field(NAME.getPreferredName(), name);
builder.field(RTN.getPreferredName(), rtn);
builder.field(PARAMETERS.getPreferredName(), parameters);
builder.endObject();
return builder;
}
public String getSortValue() {
return PainlessLookupUtility.buildPainlessMethodKey(name, parameters.size());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextInstanceBindingInfo that = (PainlessContextInstanceBindingInfo) o;
return Objects.equals(declaring, that.declaring) &&
Objects.equals(name, that.name) &&
Objects.equals(rtn, that.rtn) &&
Objects.equals(parameters, that.parameters);
}
@Override
public int hashCode() {
return Objects.hash(declaring, name, rtn, parameters);
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.painless.action;
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.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.PainlessMethod;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PainlessContextMethodInfo implements Writeable, ToXContentObject {
public static final ParseField DECLARING = new ParseField("declaring");
public static final ParseField NAME = new ParseField("name");
public static final ParseField RTN = new ParseField("return");
public static final ParseField PARAMETERS = new ParseField("parameters");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<PainlessContextMethodInfo, Void> PARSER = new ConstructingObjectParser<>(
PainlessContextMethodInfo.class.getCanonicalName(),
(v) ->
new PainlessContextMethodInfo(
(String)v[0],
(String)v[1],
(String)v[2],
(List<String>)v[3]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), DECLARING);
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareString(ConstructingObjectParser.constructorArg(), RTN);
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PARAMETERS);
}
private final String declaring;
private final String name;
private final String rtn;
private final List<String> parameters;
public PainlessContextMethodInfo(PainlessMethod painlessMethod) {
this(
painlessMethod.javaMethod.getDeclaringClass().getName(),
painlessMethod.javaMethod.getName(),
painlessMethod.returnType.getName(),
painlessMethod.typeParameters.stream().map(Class::getName).collect(Collectors.toList())
);
}
public PainlessContextMethodInfo(String declaring, String name, String rtn, List<String> parameters) {
this.declaring = Objects.requireNonNull(declaring);
this.name = Objects.requireNonNull(name);
this.rtn = Objects.requireNonNull(rtn);
this.parameters = Collections.unmodifiableList(Objects.requireNonNull(parameters));
}
public PainlessContextMethodInfo(StreamInput in) throws IOException {
declaring = in.readString();
name = in.readString();
rtn = in.readString();
parameters = Collections.unmodifiableList(in.readStringList());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(declaring);
out.writeString(name);
out.writeString(rtn);
out.writeStringCollection(parameters);
}
public static PainlessContextMethodInfo fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(DECLARING.getPreferredName(), declaring);
builder.field(NAME.getPreferredName(), name);
builder.field(RTN.getPreferredName(), rtn);
builder.field(PARAMETERS.getPreferredName(), parameters);
builder.endObject();
return builder;
}
public String getSortValue() {
return PainlessLookupUtility.buildPainlessMethodKey(name, parameters.size());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PainlessContextMethodInfo that = (PainlessContextMethodInfo) o;
return Objects.equals(declaring, that.declaring) &&
Objects.equals(name, that.name) &&
Objects.equals(rtn, that.rtn) &&
Objects.equals(parameters, that.parameters);
}
@Override
public int hashCode() {
return Objects.hash(declaring, name, rtn, parameters);
}
}

View File

@ -87,6 +87,18 @@ public final class PainlessLookup {
return classesToPainlessClasses.keySet();
}
public Set<String> getImportedPainlessMethodsKeys() {
return painlessMethodKeysToImportedPainlessMethods.keySet();
}
public Set<String> getPainlessClassBindingsKeys() {
return painlessMethodKeysToPainlessClassBindings.keySet();
}
public Set<String> getPainlessInstanceBindingsKeys() {
return painlessMethodKeysToPainlessInstanceBindings.keySet();
}
public PainlessClass lookupPainlessClass(Class<?> targetClass) {
return classesToPainlessClasses.get(targetClass);
}

View File

@ -202,6 +202,7 @@ public final class PainlessLookupUtility {
return typesStringBuilder.toString();
}
/**
* Converts a java type to a type based on the terminology specified as part of {@link PainlessLookupUtility} where if a type is an
* object class or object array, the returned type will be the equivalent def class or def array. Otherwise, this behaves as an

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.painless.action;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractSerializingTestCase;
import java.util.ArrayList;
import java.util.List;
public class ContextInfoTests extends AbstractSerializingTestCase<PainlessContextInfo> {
@Override
protected PainlessContextInfo doParseInstance(XContentParser parser) {
return PainlessContextInfo.fromXContent(parser);
}
@Override
protected PainlessContextInfo createTestInstance() {
int classesSize = randomIntBetween(20, 100);
List<PainlessContextClassInfo> classes = new ArrayList<>();
for (int clazz = 0; clazz < classesSize; ++clazz) {
int constructorsSize = randomInt(4);
List<PainlessContextConstructorInfo> constructors = new ArrayList<>(constructorsSize);
for (int constructor = 0; constructor < constructorsSize; ++constructor) {
int parameterSize = randomInt(12);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
constructors.add(new PainlessContextConstructorInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
parameters));
}
;
int staticMethodsSize = randomInt(4);
List<PainlessContextMethodInfo> staticMethods = new ArrayList<>(staticMethodsSize);
for (int staticMethod = 0; staticMethod < staticMethodsSize; ++staticMethod) {
int parameterSize = randomInt(12);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
staticMethods.add(new PainlessContextMethodInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
parameters));
}
int methodsSize = randomInt(10);
List<PainlessContextMethodInfo> methods = new ArrayList<>(methodsSize);
for (int method = 0; method < methodsSize; ++method) {
int parameterSize = randomInt(12);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
methods.add(new PainlessContextMethodInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
parameters));
}
int staticFieldsSize = randomInt(10);
List<PainlessContextFieldInfo> staticFields = new ArrayList<>();
for (int staticField = 0; staticField < staticFieldsSize; ++staticField) {
staticFields.add(new PainlessContextFieldInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10))));
}
int fieldsSize = randomInt(4);
List<PainlessContextFieldInfo> fields = new ArrayList<>();
for (int field = 0; field < fieldsSize; ++field) {
fields.add(new PainlessContextFieldInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10))));
}
classes.add(new PainlessContextClassInfo(
randomAlphaOfLength(randomIntBetween(3, 200)), randomBoolean(),
constructors, staticMethods, methods, fields, staticFields));
}
int importedMethodsSize = randomInt(4);
List<PainlessContextMethodInfo> importedMethods = new ArrayList<>(importedMethodsSize);
for (int importedMethod = 0; importedMethod < importedMethodsSize; ++importedMethod) {
int parameterSize = randomInt(12);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
importedMethods.add(new PainlessContextMethodInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
parameters));
}
int classBindingsSize = randomInt(3);
List<PainlessContextClassBindingInfo> classBindings = new ArrayList<>(classBindingsSize);
for (int classBinding = 0; classBinding < classBindingsSize; ++classBinding) {
int parameterSize = randomIntBetween(2, 5);
int readOnly = randomIntBetween(1, parameterSize - 1);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
classBindings.add(new PainlessContextClassBindingInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
readOnly,
parameters));
}
int instanceBindingsSize = randomInt(3);
List<PainlessContextInstanceBindingInfo> instanceBindings = new ArrayList<>(classBindingsSize);
for (int instanceBinding = 0; instanceBinding < instanceBindingsSize; ++instanceBinding) {
int parameterSize = randomInt(12);
List<String> parameters = new ArrayList<>(parameterSize);
for (int parameter = 0; parameter < parameterSize; ++parameter) {
parameters.add(randomAlphaOfLengthBetween(1, 20));
}
instanceBindings.add(new PainlessContextInstanceBindingInfo(
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
randomAlphaOfLength(randomIntBetween(4, 10)),
parameters));
}
return new PainlessContextInfo(randomAlphaOfLength(20),
classes, importedMethods, classBindings, instanceBindings);
}
@Override
protected Writeable.Reader<PainlessContextInfo> instanceReader() {
return PainlessContextInfo::new;
}
}

View File

@ -0,0 +1,22 @@
"Action to list contexts":
- do:
scripts_painless_context: {}
- match: { contexts.0: aggregation_selector}
- match: { contexts.22: update}
---
"Action to get all API values for score context":
- do:
scripts_painless_context:
context: score
- match: { name: score }
- match: { classes.6.name: java.lang.Appendable }
- match: { classes.6.imported: true }
- match: { classes.6.methods.0.name : append }
- match: { classes.6.methods.0.return : java.lang.Appendable }
- match: { classes.6.methods.0.parameters.0 : java.lang.CharSequence }
- match: { classes.6.methods.0.parameters.1 : int }
- match: { classes.6.methods.0.parameters.2 : int }
- match: { imported_methods.0.name: dotProduct }
- match: { class_bindings.0.name: cosineSimilarity }
- match: { instance_bindings: [] }

View File

@ -0,0 +1,17 @@
{
"scripts_painless_context": {
"methods": ["GET"],
"url": {
"path": "/_scripts/painless/_context",
"paths": ["/_scripts/painless/_context"],
"parts": {
},
"params": {
"context" : {
"type" : "string",
"description" : "Select a specific context to retrieve API information about"
}
}
}
}
}