Change Namespace for Stored Script to Only Use Id (#22206)

Currently, stored scripts use a namespace of (lang, id) to be put, get, deleted, and executed. This is not necessary since the lang is stored with the stored script. A user should only have to specify an id to use a stored script. This change makes that possible while keeping backwards compatibility with the previous namespace of (lang, id). Anywhere the previous namespace is used will log deprecation warnings.

The new behavior is the following:

When a user specifies a stored script, that script will be stored under both the new namespace and old namespace.

Take for example script 'A' with lang 'L0' and data 'D0'. If we add script 'A' to the empty set, the scripts map will be ["A" -- D0, "A#L0" -- D0]. If a script 'A' with lang 'L1' and data 'D1' is then added, the scripts map will be ["A" -- D1, "A#L1" -- D1, "A#L0" -- D0].

When a user deletes a stored script, that script will be deleted from both the new namespace (if it exists) and the old namespace.

Take for example a scripts map with {"A" -- D1, "A#L1" -- D1, "A#L0" -- D0}. If a script is removed specified by an id 'A' and lang null then the scripts map will be {"A#L0" -- D0}. To remove the final script, the deprecated namespace must be used, so an id 'A' and lang 'L0' would need to be specified.

When a user gets/executes a stored script, if the new namespace is used then the script will be retrieved/executed using only 'id', and if the old namespace is used then the script will be retrieved/executed using 'id' and 'lang'
This commit is contained in:
Jack Conradson 2017-01-31 13:27:02 -08:00 committed by GitHub
parent eb36b82de4
commit 3d2626c4c6
59 changed files with 2963 additions and 1003 deletions

View File

@ -886,7 +886,7 @@
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]versioning[/\\]SimpleVersioningIT.java" checks="LineLength" />
<suppress files="modules[/\\]lang-expression[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]expression[/\\]ExpressionScriptEngineService.java" checks="LineLength" />
<suppress files="modules[/\\]lang-expression[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]expression[/\\]ExpressionTests.java" checks="LineLength" />
<suppress files="modules[/\\]lang-expression[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]expression[/\\]IndexedExpressionTests.java" checks="LineLength" />
<suppress files="modules[/\\]lang-expression[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]expression[/\\]StoredExpressionTests.java" checks="LineLength" />
<suppress files="modules[/\\]lang-expression[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]expression[/\\]MoreExpressionTests.java" checks="LineLength" />
<suppress files="modules[/\\]lang-groovy[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]groovy[/\\]GroovyScriptEngineService.java" checks="LineLength" />
<suppress files="modules[/\\]lang-groovy[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]script[/\\]groovy[/\\]GroovySecurityTests.java" checks="LineLength" />

View File

@ -31,66 +31,79 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
public class DeleteStoredScriptRequest extends AcknowledgedRequest<DeleteStoredScriptRequest> {
private String id;
private String scriptLang;
private String lang;
DeleteStoredScriptRequest() {
super();
}
public DeleteStoredScriptRequest(String scriptLang, String id) {
this.scriptLang = scriptLang;
public DeleteStoredScriptRequest(String id, String lang) {
super();
this.id = id;
this.lang = lang;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (id == null) {
validationException = addValidationError("id is missing", validationException);
if (id == null || id.isEmpty()) {
validationException = addValidationError("must specify id for stored script", validationException);
} else if (id.contains("#")) {
validationException = addValidationError("id can't contain: '#'", validationException);
validationException = addValidationError("id cannot contain '#' for stored script", validationException);
}
if (scriptLang == null) {
validationException = addValidationError("lang is missing", validationException);
} else if (scriptLang.contains("#")) {
validationException = addValidationError("lang can't contain: '#'", validationException);
if (lang != null && lang.contains("#")) {
validationException = addValidationError("lang cannot contain '#' for stored script", validationException);
}
return validationException;
}
public String scriptLang() {
return scriptLang;
}
public DeleteStoredScriptRequest scriptLang(String type) {
this.scriptLang = type;
return this;
}
public String id() {
return id;
}
public DeleteStoredScriptRequest id(String id) {
this.id = id;
return this;
}
public String lang() {
return lang;
}
public DeleteStoredScriptRequest lang(String lang) {
this.lang = lang;
return this;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
scriptLang = in.readString();
lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(scriptLang);
out.writeString(lang == null ? "" : lang);
out.writeString(id);
}
@Override
public String toString() {
return "delete script {[" + scriptLang + "][" + id + "]}";
return "delete stored script {id [" + id + "]" + (lang != null ? ", lang [" + lang + "]" : "") + "}";
}
}

View File

@ -29,13 +29,15 @@ public class DeleteStoredScriptRequestBuilder extends AcknowledgedRequestBuilder
super(client, action, new DeleteStoredScriptRequest());
}
public DeleteStoredScriptRequestBuilder setScriptLang(String scriptLang) {
request.scriptLang(scriptLang);
public DeleteStoredScriptRequestBuilder setLang(String lang) {
request.lang(lang);
return this;
}
public DeleteStoredScriptRequestBuilder setId(String id) {
request.id(id);
return this;
}

View File

@ -28,61 +28,79 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import static org.elasticsearch.action.ValidateActions.addValidationError;
public class GetStoredScriptRequest extends MasterNodeReadRequest<GetStoredScriptRequest> {
protected String id;
protected String lang;
GetStoredScriptRequest() {
super();
}
public GetStoredScriptRequest(String lang, String id) {
this.lang = lang;
public GetStoredScriptRequest(String id, String lang) {
super();
this.id = id;
this.lang = lang;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (lang == null) {
validationException = ValidateActions.addValidationError("lang is missing", validationException);
if (id == null || id.isEmpty()) {
validationException = addValidationError("must specify id for stored script", validationException);
} else if (id.contains("#")) {
validationException = addValidationError("id cannot contain '#' for stored script", validationException);
}
if (id == null) {
validationException = ValidateActions.addValidationError("id is missing", validationException);
if (lang != null && lang.contains("#")) {
validationException = addValidationError("lang cannot contain '#' for stored script", validationException);
}
return validationException;
}
public GetStoredScriptRequest lang(@Nullable String type) {
this.lang = type;
return this;
}
public GetStoredScriptRequest id(String id) {
this.id = id;
return this;
}
public String lang() {
return lang;
}
public String id() {
return id;
}
public GetStoredScriptRequest id(String id) {
this.id = id;
return this;
}
public String lang() {
return lang;
}
public GetStoredScriptRequest lang(String lang) {
this.lang = lang;
return this;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(lang);
out.writeString(lang == null ? "" : lang);
out.writeString(id);
}

View File

@ -19,49 +19,70 @@
package org.elasticsearch.action.admin.cluster.storedscripts;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.StoredScriptSource;
import java.io.IOException;
public class GetStoredScriptResponse extends ActionResponse implements ToXContent {
private String storedScript;
private StoredScriptSource source;
GetStoredScriptResponse() {
}
GetStoredScriptResponse(String storedScript) {
this.storedScript = storedScript;
GetStoredScriptResponse(StoredScriptSource source) {
this.source = source;
}
/**
* @return if a stored script and if not found <code>null</code>
*/
public String getStoredScript() {
return storedScript;
public StoredScriptSource getSource() {
return source;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.value(storedScript);
source.toXContent(builder, params);
return builder;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
storedScript = in.readOptionalString();
if (in.readBoolean()) {
if (in.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
source = new StoredScriptSource(in);
} else {
source = new StoredScriptSource(in.readString());
}
} else {
source = null;
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalString(storedScript);
if (source == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
if (out.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
source.writeTo(out);
} else {
out.writeString(source.getCode());
}
}
}
}

View File

@ -33,94 +33,105 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
public class PutStoredScriptRequest extends AcknowledgedRequest<PutStoredScriptRequest> {
private String id;
private String scriptLang;
private BytesReference script;
private String lang;
private BytesReference content;
public PutStoredScriptRequest() {
super();
}
public PutStoredScriptRequest(String scriptLang) {
public PutStoredScriptRequest(String id, String lang, BytesReference content) {
super();
this.scriptLang = scriptLang;
}
public PutStoredScriptRequest(String scriptLang, String id) {
super();
this.scriptLang = scriptLang;
this.id = id;
this.lang = lang;
this.content = content;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (id == null) {
validationException = addValidationError("id is missing", validationException);
if (id == null || id.isEmpty()) {
validationException = addValidationError("must specify id for stored script", validationException);
} else if (id.contains("#")) {
validationException = addValidationError("id can't contain: '#'", validationException);
validationException = addValidationError("id cannot contain '#' for stored script", validationException);
}
if (scriptLang == null) {
validationException = addValidationError("lang is missing", validationException);
} else if (scriptLang.contains("#")) {
validationException = addValidationError("lang can't contain: '#'", validationException);
if (lang != null && lang.contains("#")) {
validationException = addValidationError("lang cannot contain '#' for stored script", validationException);
}
if (script == null) {
validationException = addValidationError("script is missing", validationException);
if (content == null) {
validationException = addValidationError("must specify code for stored script", validationException);
}
return validationException;
}
public String scriptLang() {
return scriptLang;
}
public PutStoredScriptRequest scriptLang(String scriptLang) {
this.scriptLang = scriptLang;
return this;
}
public String id() {
return id;
}
public PutStoredScriptRequest id(String id) {
this.id = id;
return this;
}
public BytesReference script() {
return script;
public String lang() {
return lang;
}
public PutStoredScriptRequest script(BytesReference source) {
this.script = source;
public PutStoredScriptRequest lang(String lang) {
this.lang = lang;
return this;
}
public BytesReference content() {
return content;
}
public PutStoredScriptRequest content(BytesReference content) {
this.content = content;
return this;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
scriptLang = in.readString();
lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readOptionalString();
script = in.readBytesReference();
content = in.readBytesReference();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(scriptLang);
out.writeString(lang == null ? "" : lang);
out.writeOptionalString(id);
out.writeBytesReference(script);
out.writeBytesReference(content);
}
@Override
public String toString() {
String sSource = "_na_";
String source = "_na_";
try {
sSource = XContentHelper.convertToJson(script, false);
} catch (Exception e) {
source = XContentHelper.convertToJson(content, false);
} catch (Exception exception) {
// ignore
}
return "put script {[" + id + "][" + scriptLang + "], script[" + sSource + "]}";
return "put stored script {id [" + id + "]" + (lang != null ? ", lang [" + lang + "]" : "") + ", content [" + source + "]}";
}
}

View File

@ -30,19 +30,21 @@ public class PutStoredScriptRequestBuilder extends AcknowledgedRequestBuilder<Pu
super(client, action, new PutStoredScriptRequest());
}
public PutStoredScriptRequestBuilder setScriptLang(String scriptLang) {
request.scriptLang(scriptLang);
return this;
}
public PutStoredScriptRequestBuilder setId(String id) {
request.id(id);
return this;
}
public PutStoredScriptRequestBuilder setSource(BytesReference source) {
request.script(source);
public PutStoredScriptRequestBuilder setLang(String lang) {
request.lang(lang);
return this;
}
public PutStoredScriptRequestBuilder setContent(BytesReference content) {
request.content(content);
return this;
}
}

View File

@ -59,7 +59,7 @@ public class TransportPutStoredScriptAction extends TransportMasterNodeAction<Pu
@Override
protected void masterOperation(PutStoredScriptRequest request, ClusterState state,
ActionListener<PutStoredScriptResponse> listener) throws Exception {
scriptService.storeScript(clusterService, request, listener);
scriptService.putStoredScript(clusterService, request, listener);
}
@Override

View File

@ -761,7 +761,7 @@ public abstract class AbstractAsyncBulkByScrollAction<Request extends AbstractBu
return request;
}
if (executable == null) {
CompiledScript compiled = scriptService.compile(script, ScriptContext.Standard.UPDATE, emptyMap());
CompiledScript compiled = scriptService.compile(script, ScriptContext.Standard.UPDATE);
executable = scriptService.executable(compiled, params);
}
if (context == null) {

View File

@ -1199,7 +1199,7 @@ public abstract class AbstractClient extends AbstractComponent implements Client
@Override
public DeleteStoredScriptRequestBuilder prepareDeleteStoredScript(@Nullable String scriptLang, String id){
return prepareDeleteStoredScript().setScriptLang(scriptLang).setId(id);
return prepareDeleteStoredScript().setLang(scriptLang).setId(id);
}
}

View File

@ -341,7 +341,7 @@ public class QueryShardContext extends QueryRewriteContext {
*/
public final Function<Map<String, Object>, SearchScript> getLazySearchScript(Script script, ScriptContext context) {
failIfFrozen();
CompiledScript compile = scriptService.compile(script, context, script.getOptions());
CompiledScript compile = scriptService.compile(script, context);
return (p) -> scriptService.search(lookup(), compile, p);
}
@ -360,7 +360,7 @@ public class QueryShardContext extends QueryRewriteContext {
*/
public final Function<Map<String, Object>, ExecutableScript> getLazyExecutableScript(Script script, ScriptContext context) {
failIfFrozen();
CompiledScript executable = scriptService.compile(script, context, script.getOptions());
CompiledScript executable = scriptService.compile(script, context);
return (p) -> scriptService.executable(executable, p);
}

View File

@ -44,10 +44,7 @@ public class InternalTemplateService implements TemplateService {
int mustacheEnd = template.indexOf("}}");
if (mustacheStart != -1 && mustacheEnd != -1 && mustacheStart < mustacheEnd) {
Script script = new Script(ScriptType.INLINE, "mustache", template, Collections.emptyMap());
CompiledScript compiledScript = scriptService.compile(
script,
ScriptContext.Standard.INGEST,
Collections.emptyMap());
CompiledScript compiledScript = scriptService.compile(script, ScriptContext.Standard.INGEST);
return new Template() {
@Override
public String execute(Map<String, Object> model) {

View File

@ -31,25 +31,35 @@ import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteStoredScriptAction extends BaseRestHandler {
public RestDeleteStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestDeleteStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
super(settings);
if (registerDefaultHandlers) {
controller.registerHandler(DELETE, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptLang(RestRequest request) {
return request.param("lang");
// Note {lang} is actually {id} in the first handler. It appears
// parameters as part of the path must be of the same ordering relative
// to name or they will not work as expected.
controller.registerHandler(DELETE, "/_scripts/{lang}", this);
controller.registerHandler(DELETE, "/_scripts/{lang}/{id}", this);
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException {
DeleteStoredScriptRequest deleteStoredScriptRequest = new DeleteStoredScriptRequest(getScriptLang(request), request.param("id"));
return channel -> client.admin().cluster().deleteStoredScript(deleteStoredScriptRequest, new AcknowledgedRestListener<>(channel));
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
String id = request.param("id");
String lang = request.param("lang");
// In the case where only {lang} is not null, we make it {id} because of
// name ordering issues in the handlers' paths.
if (id == null) {
id = lang;
lang = null;
}
if (lang != null) {
deprecationLogger.deprecated(
"specifying lang [" + lang + "] as part of the url path is deprecated");
}
DeleteStoredScriptRequest deleteStoredScriptRequest = new DeleteStoredScriptRequest(id, lang);
return channel -> client.admin().cluster().deleteStoredScript(deleteStoredScriptRequest, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.rest.action.admin.cluster;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
@ -30,57 +31,84 @@ import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.script.StoredScriptSource;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.GET;
public class RestGetStoredScriptAction extends BaseRestHandler {
public static final ParseField _ID_PARSE_FIELD = new ParseField("_id");
public static final ParseField FOUND_PARSE_FIELD = new ParseField("found");
public RestGetStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestGetStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
super(settings);
if (registerDefaultHandlers) {
// Note {lang} is actually {id} in the first handler. It appears
// parameters as part of the path must be of the same ordering relative
// to name or they will not work as expected.
controller.registerHandler(GET, "/_scripts/{lang}", this);
controller.registerHandler(GET, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptFieldName() {
return Fields.SCRIPT;
}
protected String getScriptLang(RestRequest request) {
return request.param("lang");
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException {
final GetStoredScriptRequest getRequest = new GetStoredScriptRequest(getScriptLang(request), request.param("id"));
String id;
String lang;
// In the case where only {lang} is not null, we make it {id} because of
// name ordering issues in the handlers' paths.
if (request.param("id") == null) {
id = request.param("lang");;
lang = null;
} else {
id = request.param("id");
lang = request.param("lang");
}
if (lang != null) {
deprecationLogger.deprecated(
"specifying lang [" + lang + "] as part of the url path is deprecated");
}
GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id, lang);
return channel -> client.admin().cluster().getStoredScript(getRequest, new RestBuilderListener<GetStoredScriptResponse>(channel) {
@Override
public RestResponse buildResponse(GetStoredScriptResponse response, XContentBuilder builder) throws Exception {
builder.startObject();
builder.field(Fields.LANG, getRequest.lang());
builder.field(Fields._ID, getRequest.id());
boolean found = response.getStoredScript() != null;
builder.field(Fields.FOUND, found);
RestStatus status = RestStatus.NOT_FOUND;
if (found) {
builder.field(getScriptFieldName(), response.getStoredScript());
status = RestStatus.OK;
builder.field(_ID_PARSE_FIELD.getPreferredName(), id);
if (lang != null) {
builder.field(StoredScriptSource.LANG_PARSE_FIELD.getPreferredName(), lang);
}
StoredScriptSource source = response.getSource();
boolean found = source != null;
builder.field(FOUND_PARSE_FIELD.getPreferredName(), found);
if (found) {
if (lang == null) {
builder.startObject(StoredScriptSource.SCRIPT_PARSE_FIELD.getPreferredName());
builder.field(StoredScriptSource.LANG_PARSE_FIELD.getPreferredName(), source.getLang());
builder.field(StoredScriptSource.CODE_PARSE_FIELD.getPreferredName(), source.getCode());
if (source.getOptions().isEmpty() == false) {
builder.field(StoredScriptSource.OPTIONS_PARSE_FIELD.getPreferredName(), source.getOptions());
}
builder.endObject();
return new BytesRestResponse(status, builder);
} else {
builder.field(StoredScriptSource.SCRIPT_PARSE_FIELD.getPreferredName(), source.getCode());
}
}
builder.endObject();
return new BytesRestResponse(found ? RestStatus.OK : RestStatus.NOT_FOUND, builder);
}
});
}
private static final class Fields {
private static final String SCRIPT = "script";
private static final String LANG = "lang";
private static final String _ID = "_id";
private static final String FOUND = "found";
}
}

View File

@ -20,6 +20,7 @@ package org.elasticsearch.rest.action.admin.cluster;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
@ -32,26 +33,39 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutStoredScriptAction extends BaseRestHandler {
public RestPutStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestPutStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
public RestPutStoredScriptAction(Settings settings, RestController controller) {
super(settings);
if (registerDefaultHandlers) {
// Note {lang} is actually {id} in the first two handlers. It appears
// parameters as part of the path must be of the same ordering relative
// to name or they will not work as expected.
controller.registerHandler(POST, "/_scripts/{lang}", this);
controller.registerHandler(PUT, "/_scripts/{lang}", this);
controller.registerHandler(POST, "/_scripts/{lang}/{id}", this);
controller.registerHandler(PUT, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptLang(RestRequest request) {
return request.param("lang");
}
@Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException {
PutStoredScriptRequest putRequest = new PutStoredScriptRequest(getScriptLang(request), request.param("id"));
putRequest.script(request.content());
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
String id = request.param("id");
String lang = request.param("lang");
// In the case where only {lang} is not null, we make it {id} because of
// name ordering issues in the handlers' paths.
if (id == null) {
id = lang;
lang = null;
}
BytesReference content = request.content();
if (lang != null) {
deprecationLogger.deprecated(
"specifying lang [" + lang + "] as part of the url path is deprecated, use request content instead");
}
PutStoredScriptRequest putRequest = new PutStoredScriptRequest(id, lang, content);
return channel -> client.admin().cluster().putStoredScript(putRequest, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -19,12 +19,15 @@
package org.elasticsearch.script;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesArray;
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.logging.DeprecationLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContentObject;
@ -42,12 +45,58 @@ import java.util.Map;
import java.util.Objects;
/**
* Script represents used-defined input that can be used to
* {@link Script} represents used-defined input that can be used to
* compile and execute a script from the {@link ScriptService}
* based on the {@link ScriptType}.
*
* There are three types of scripts specified by {@link ScriptType}.
*
* The following describes the expected parameters for each type of script:
*
* <ul>
* <li> {@link ScriptType#INLINE}
* <ul>
* <li> {@link Script#lang} - specifies the language, defaults to {@link Script#DEFAULT_SCRIPT_LANG}
* <li> {@link Script#idOrCode} - specifies the code to be compiled, must not be {@code null}
* <li> {@link Script#options} - specifies the compiler options for this script; must not be {@code null},
* use an empty {@link Map} to specify no options
* <li> {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
* </ul>
* <li> {@link ScriptType#STORED}
* <ul>
* <li> {@link Script#lang} - the language will be specified when storing the script, so this should
* be {@code null}; however, this can be specified to look up a stored
* script as part of the deprecated API
* <li> {@link Script#idOrCode} - specifies the id of the stored script to be looked up, must not be {@code null}
* <li> {@link Script#options} - compiler options will be specified when a stored script is stored,
* so they have no meaning here and must be {@code null}
* <li> {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
* </ul>
* <li> {@link ScriptType#FILE}
* <ul>
* <li> {@link Script#lang} - specifies the language for look up, defaults to {@link Script#DEFAULT_SCRIPT_LANG}
* <li> {@link Script#idOrCode} - specifies the id of the file script to be looked up, must not be {@code null}
* <li> {@link Script#options} - compiler options will be specified when a file script is loaded,
* so they have no meaning here and must be {@code null}
* <li> {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
* </ul>
* </ul>
*/
public final class Script implements ToXContentObject, Writeable {
/**
* Standard logger necessary for allocation of the deprecation logger.
*/
private static final Logger LOGGER = ESLoggerFactory.getLogger(ScriptMetaData.class);
/**
* Deprecation logger necessary for namespace changes related to stored scripts.
*/
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(LOGGER);
/**
* The name of the of the default scripting language.
*/
@ -196,15 +245,62 @@ public final class Script implements ToXContentObject, Writeable {
"or [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
}
if (type == ScriptType.INLINE) {
if (lang == null) {
lang = defaultLang;
}
if (idOrCode == null) {
throw new IllegalArgumentException("must specify an id or code for a script");
throw new IllegalArgumentException(
"must specify <id> for an [" + ScriptType.INLINE.getParseField().getPreferredName() + "] script");
}
if (options.size() > 1 || options.size() == 1 && options.get(CONTENT_TYPE_OPTION) == null) {
options.remove(CONTENT_TYPE_OPTION);
throw new IllegalArgumentException("illegal compiler options [" + options + "] specified");
}
} else if (type == ScriptType.STORED) {
// Only issue this deprecation warning if we aren't using a template. Templates during
// this deprecation phase must always specify the default template language or they would
// possibly pick up a script in a different language as defined by the user under the new
// namespace unintentionally.
if (lang != null && lang.equals(DEFAULT_TEMPLATE_LANG) == false) {
DEPRECATION_LOGGER.deprecated("specifying the field [" + LANG_PARSE_FIELD.getPreferredName() + "] " +
"for executing " + ScriptType.STORED + " scripts is deprecated; use only the field " +
"[" + ScriptType.STORED.getParseField().getPreferredName() + "] to specify an <id>");
}
return new Script(type, this.lang == null ? defaultLang : this.lang, idOrCode, options, params);
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify <code> for an [" + ScriptType.STORED.getParseField().getPreferredName() + "] script");
}
if (options.isEmpty()) {
options = null;
} else {
throw new IllegalArgumentException("field [" + OPTIONS_PARSE_FIELD.getPreferredName() + "] " +
"cannot be specified using a [" + ScriptType.STORED.getParseField().getPreferredName() + "] script");
}
} else if (type == ScriptType.FILE) {
if (lang == null) {
lang = defaultLang;
}
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify <code> for an [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
}
if (options.isEmpty()) {
options = null;
} else {
throw new IllegalArgumentException("field [" + OPTIONS_PARSE_FIELD.getPreferredName() + "] " +
"cannot be specified using a [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
}
}
return new Script(type, lang, idOrCode, options, params);
}
}
@ -292,6 +388,7 @@ public final class Script implements ToXContentObject, Writeable {
* @param defaultLang The default language to use if no language is specified. The default language isn't necessarily
* the one defined by {@link Script#DEFAULT_SCRIPT_LANG} due to backwards compatiblity requirements
* related to stored queries using previously default languauges.
*
* @return The parsed {@link Script}.
*/
public static Script parse(XContentParser parser, String defaultLang) throws IOException {
@ -327,34 +424,57 @@ public final class Script implements ToXContentObject, Writeable {
/**
* Constructor for a script that does not need to use compiler options.
* @param type The {@link ScriptType}.
* @param lang The lang for this {@link Script}.
* @param lang The language for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE} or
* {@link ScriptType#FILE}. For {@link ScriptType#STORED} scripts this should be null, but can
* be specified to access scripts stored as part of the stored scripts deprecated API.
* @param idOrCode The id for this {@link Script} if the {@link ScriptType} is {@link ScriptType#FILE} or {@link ScriptType#STORED}.
* The code for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE}.
* @param params The user-defined params to be bound for script execution.
*/
public Script(ScriptType type, String lang, String idOrCode, Map<String, Object> params) {
this(type, lang, idOrCode, Collections.emptyMap(), params);
this(type, lang, idOrCode, type == ScriptType.INLINE ? Collections.emptyMap() : null, params);
}
/**
* Constructor for a script that requires the use of compiler options.
* @param type The {@link ScriptType}.
* @param lang The lang for this {@link Script}.
* @param lang The language for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE} or
* {@link ScriptType#FILE}. For {@link ScriptType#STORED} scripts this should be null, but can
* be specified to access scripts stored as part of the stored scripts deprecated API.
* @param idOrCode The id for this {@link Script} if the {@link ScriptType} is {@link ScriptType#FILE} or {@link ScriptType#STORED}.
* The code for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE}.
* @param options The options to be passed to the compiler for use at compile-time.
* @param options The map of compiler options for this {@link Script} if the {@link ScriptType}
* is {@link ScriptType#INLINE}, {@code null} otherwise.
* @param params The user-defined params to be bound for script execution.
*/
public Script(ScriptType type, String lang, String idOrCode, Map<String, String> options, Map<String, Object> params) {
this.idOrCode = Objects.requireNonNull(idOrCode);
this.type = Objects.requireNonNull(type);
this.lang = Objects.requireNonNull(lang);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
this.idOrCode = Objects.requireNonNull(idOrCode);
this.params = Collections.unmodifiableMap(Objects.requireNonNull(params));
if (type != ScriptType.INLINE && !options.isEmpty()) {
throw new IllegalArgumentException(
"Compiler options [" + options + "] cannot be specified at runtime for [" + type + "] scripts.");
if (type == ScriptType.INLINE) {
this.lang = Objects.requireNonNull(lang);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
} else if (type == ScriptType.STORED) {
this.lang = lang;
if (options != null) {
throw new IllegalStateException(
"options must be null for [" + ScriptType.STORED.getParseField().getPreferredName() + "] scripts");
}
this.options = null;
} else if (type == ScriptType.FILE) {
this.lang = Objects.requireNonNull(lang);
if (options != null) {
throw new IllegalStateException(
"options must be null for [" + ScriptType.FILE.getParseField().getPreferredName() + "] scripts");
}
this.options = null;
} else {
throw new IllegalStateException("unknown script type [" + type.getName() + "]");
}
}
@ -362,82 +482,125 @@ public final class Script implements ToXContentObject, Writeable {
* Creates a {@link Script} read from an input stream.
*/
public Script(StreamInput in) throws IOException {
// Version 5.1+ requires all Script members to be non-null and supports the potential
// for more options than just XContentType. Reorders the read in contents to be in
// same order as the constructor.
if (in.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
// Version 5.3 allows lang to be an optional parameter for stored scripts and expects
// options to be null for stored and file scripts.
if (in.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
this.type = ScriptType.readFrom(in);
this.lang = in.readString();
this.lang = in.readOptionalString();
this.idOrCode = in.readString();
@SuppressWarnings("unchecked")
Map<String, String> options = (Map<String, String>)(Map)in.readMap();
this.options = options;
this.params = in.readMap();
// Version 5.1 to 5.3 (exclusive) requires all Script members to be non-null and supports the potential
// for more options than just XContentType. Reorders the read in contents to be in
// same order as the constructor.
} else if (in.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
this.type = ScriptType.readFrom(in);
this.lang = in.readString();
this.idOrCode = in.readString();
@SuppressWarnings("unchecked")
Map<String, String> options = (Map<String, String>)(Map)in.readMap();
if (this.type != ScriptType.INLINE && options.isEmpty()) {
this.options = null;
} else {
this.options = options;
}
this.params = in.readMap();
// Prior to version 5.1 the script members are read in certain cases as optional and given
// default values when necessary. Also the only option supported is for XContentType.
} else {
String idOrCode = in.readString();
ScriptType type;
this.idOrCode = in.readString();
if (in.readBoolean()) {
type = ScriptType.readFrom(in);
this.type = ScriptType.readFrom(in);
} else {
type = DEFAULT_SCRIPT_TYPE;
this.type = DEFAULT_SCRIPT_TYPE;
}
String lang = in.readOptionalString();
if (lang == null) {
lang = DEFAULT_SCRIPT_LANG;
this.lang = DEFAULT_SCRIPT_LANG;
} else {
this.lang = lang;
}
Map<String, Object> params = in.readMap();
if (params == null) {
params = new HashMap<>();
this.params = new HashMap<>();
} else {
this.params = params;
}
Map<String, String> options = new HashMap<>();
if (in.readBoolean()) {
this.options = new HashMap<>();
XContentType contentType = XContentType.readFrom(in);
options.put(CONTENT_TYPE_OPTION, contentType.mediaType());
this.options.put(CONTENT_TYPE_OPTION, contentType.mediaType());
} else if (type == ScriptType.INLINE) {
options = new HashMap<>();
} else {
this.options = null;
}
this.type = type;
this.lang = lang;
this.idOrCode = idOrCode;
this.options = options;
this.params = params;
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
// Version 5.1+ requires all Script members to be non-null and supports the potential
// for more options than just XContentType. Reorders the written out contents to be in
// same order as the constructor.
if (out.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
// Version 5.3+ allows lang to be an optional parameter for stored scripts and expects
// options to be null for stored and file scripts.
if (out.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
type.writeTo(out);
out.writeString(lang);
out.writeOptionalString(lang);
out.writeString(idOrCode);
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>)(Map)this.options;
out.writeMap(options);
out.writeMap(params);
// Prior to version 5.1 the Script members were possibly written as optional or null, though this is no longer
// necessary since Script members cannot be null anymore, and there is no case where a null value wasn't equivalent
// to it's default value when actually compiling/executing a script. Meaning, there are no backwards compatibility issues,
// and now there's enforced consistency. Also the only supported compiler option was XContentType.
// Version 5.1 to 5.3 (exclusive) requires all Script members to be non-null and supports the potential
// for more options than just XContentType. Reorders the written out contents to be in
// same order as the constructor.
} else if (out.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
type.writeTo(out);
if (lang == null) {
out.writeString("");
} else {
out.writeString(lang);
}
out.writeString(idOrCode);
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>)(Map)this.options;
if (options == null) {
out.writeMap(new HashMap<>());
} else {
out.writeMap(options);
}
out.writeMap(params);
// Prior to version 5.1 the Script members were possibly written as optional or null, though there is no case where a null
// value wasn't equivalent to it's default value when actually compiling/executing a script. Meaning, there are no
// backwards compatibility issues, and now there's enforced consistency. Also the only supported compiler
// option was XContentType.
} else {
out.writeString(idOrCode);
out.writeBoolean(true);
type.writeTo(out);
out.writeBoolean(true);
out.writeString(lang);
out.writeMap(params.isEmpty() ? null : params);
out.writeOptionalString(lang);
if (options.containsKey(CONTENT_TYPE_OPTION)) {
if (params.isEmpty()) {
out.writeMap(null);
} else {
out.writeMap(params);
}
if (options != null && options.containsKey(CONTENT_TYPE_OPTION)) {
XContentType contentType = XContentType.fromMediaTypeOrFormat(options.get(CONTENT_TYPE_OPTION));
out.writeBoolean(true);
contentType.writeTo(out);
@ -478,7 +641,7 @@ public final class Script implements ToXContentObject, Writeable {
* }
* }
*
* Note that options and params will only be included if there have been any specified.
* Note that lang, options, and params will only be included if there have been any specified.
*
* This also handles templates in a special way. If the {@link Script#CONTENT_TYPE_OPTION} option
* is provided and the {@link ScriptType#INLINE} is specified then the template will be preserved as a raw field.
@ -504,7 +667,7 @@ public final class Script implements ToXContentObject, Writeable {
public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException {
builder.startObject();
String contentType = options.get(CONTENT_TYPE_OPTION);
String contentType = options == null ? null : options.get(CONTENT_TYPE_OPTION);
if (type == ScriptType.INLINE && contentType != null && builder.contentType().mediaType().equals(contentType)) {
builder.rawField(type.getParseField().getPreferredName(), new BytesArray(idOrCode));
@ -512,9 +675,11 @@ public final class Script implements ToXContentObject, Writeable {
builder.field(type.getParseField().getPreferredName(), idOrCode);
}
if (lang != null) {
builder.field(LANG_PARSE_FIELD.getPreferredName(), lang);
}
if (!options.isEmpty()) {
if (options != null && !options.isEmpty()) {
builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options);
}
@ -527,6 +692,22 @@ public final class Script implements ToXContentObject, Writeable {
return builder;
}
/**
* @return The {@link ScriptType} for this {@link Script}.
*/
public ScriptType getType() {
return type;
}
/**
* @return The language for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE} or
* {@link ScriptType#FILE}. For {@link ScriptType#STORED} scripts this should be null, but can
* be specified to access scripts stored as part of the stored scripts deprecated API.
*/
public String getLang() {
return lang;
}
/**
* @return The id for this {@link Script} if the {@link ScriptType} is {@link ScriptType#FILE} or {@link ScriptType#STORED}.
* The code for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE}.
@ -536,21 +717,8 @@ public final class Script implements ToXContentObject, Writeable {
}
/**
* @return The {@link ScriptType} for this {@link Script}.
*/
public ScriptType getType() {
return type;
}
/**
* @return The language for this {@link Script}.
*/
public String getLang() {
return lang;
}
/**
* @return The map of compiler options for this {@link Script}.
* @return The map of compiler options for this {@link Script} if the {@link ScriptType}
* is {@link ScriptType#INLINE}, {@code null} otherwise.
*/
public Map<String, String> getOptions() {
return options;
@ -571,9 +739,9 @@ public final class Script implements ToXContentObject, Writeable {
Script script = (Script)o;
if (type != script.type) return false;
if (!lang.equals(script.lang)) return false;
if (lang != null ? !lang.equals(script.lang) : script.lang != null) return false;
if (!idOrCode.equals(script.idOrCode)) return false;
if (!options.equals(script.options)) return false;
if (options != null ? !options.equals(script.options) : script.options != null) return false;
return params.equals(script.params);
}
@ -581,9 +749,9 @@ public final class Script implements ToXContentObject, Writeable {
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + lang.hashCode();
result = 31 * result + (lang != null ? lang.hashCode() : 0);
result = 31 * result + idOrCode.hashCode();
result = 31 * result + options.hashCode();
result = 31 * result + (options != null ? options.hashCode() : 0);
result = 31 * result + params.hashCode();
return result;
}

View File

@ -18,24 +18,24 @@
*/
package org.elasticsearch.script;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.Collections;
@ -43,68 +43,123 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
public final class ScriptMetaData implements MetaData.Custom {
/**
* {@link ScriptMetaData} is used to store user-defined scripts
* as part of the {@link ClusterState}. Currently scripts can
* be stored as part of the new namespace for a stored script where
* only an id is used or as part of the deprecated namespace where
* both a language and an id are used.
*/
public final class ScriptMetaData implements MetaData.Custom, Writeable, ToXContent {
public static final String TYPE = "stored_scripts";
/**
* A builder used to modify the currently stored scripts data held within
* the {@link ClusterState}. Scripts can be added or deleted, then built
* to generate a new {@link Map} of scripts that will be used to update
* the current {@link ClusterState}.
*/
public static final class Builder {
private final Map<String, ScriptAsBytes> scripts;
private final Map<String, StoredScriptSource> scripts;
ScriptMetaData(Map<String, ScriptAsBytes> scripts) {
this.scripts = scripts;
/**
* @param previous The current {@link ScriptMetaData} or {@code null} if there
* is no existing {@link ScriptMetaData}.
*/
public Builder(ScriptMetaData previous) {
this.scripts = previous == null ? new HashMap<>() :new HashMap<>(previous.scripts);
}
public BytesReference getScriptAsBytes(String language, String id) {
ScriptAsBytes scriptAsBytes = scripts.get(toKey(language, id));
if (scriptAsBytes != null) {
return scriptAsBytes.script;
} else {
return null;
/**
* Add a new script to the existing stored scripts. The script will be added under
* both the new namespace and the deprecated namespace, so that look ups under
* the deprecated namespace will continue to work. Should a script already exist under
* the new namespace using a different language, it will be replaced and a deprecation
* warning will be issued. The replaced script will still exist under the deprecated
* namespace and can continue to be looked up this way until it is deleted.
* <p>
* Take for example script 'A' with lang 'L0' and data 'D0'. If we add script 'A' to the
* empty set, the scripts {@link Map} will be ["A" -- D0, "A#L0" -- D0]. If a script
* 'A' with lang 'L1' and data 'D1' is then added, the scripts {@link Map} will be
* ["A" -- D1, "A#L1" -- D1, "A#L0" -- D0].
* @param id The user-specified id to use for the look up.
* @param source The user-specified stored script data held in {@link StoredScriptSource}.
*/
public Builder storeScript(String id, StoredScriptSource source) {
StoredScriptSource previous = scripts.put(id, source);
scripts.put(source.getLang() + "#" + id, source);
if (previous != null && previous.getLang().equals(source.getLang()) == false) {
DEPRECATION_LOGGER.deprecated("stored script [" + id + "] already exists using a different lang " +
"[" + previous.getLang() + "], the new namespace for stored scripts will only use (id) instead of (lang, id)");
}
return this;
}
/**
* Delete a script from the existing stored scripts. The script will be removed from the
* new namespace if the script language matches the current script under the same id or
* if the script language is {@code null}. The script will be removed from the deprecated
* namespace on any delete either using using the specified lang parameter or the language
* found from looking up the script in the new namespace.
* <p>
* Take for example a scripts {@link Map} with {"A" -- D1, "A#L1" -- D1, "A#L0" -- D0}.
* If a script is removed specified by an id 'A' and lang {@code null} then the scripts
* {@link Map} will be {"A#L0" -- D0}. To remove the final script, the deprecated
* namespace must be used, so an id 'A' and lang 'L0' would need to be specified.
* @param id The user-specified id to use for the look up.
* @param lang The user-specified language to use for the look up if using the deprecated
* namespace, otherwise {@code null}.
*/
public Builder deleteScript(String id, String lang) {
StoredScriptSource source = scripts.get(id);
if (lang == null) {
if (source == null) {
throw new ResourceNotFoundException("stored script [" + id + "] does not exist and cannot be deleted");
}
lang = source.getLang();
}
if (source != null) {
if (lang.equals(source.getLang())) {
scripts.remove(id);
}
}
public String getScript(String language, String id) {
BytesReference scriptAsBytes = getScriptAsBytes(language, id);
if (scriptAsBytes == null) {
return null;
}
return scriptAsBytes.utf8ToString();
source = scripts.get(lang + "#" + id);
if (source == null) {
throw new ResourceNotFoundException(
"stored script [" + id + "] using lang [" + lang + "] does not exist and cannot be deleted");
}
public static String parseStoredScript(BytesReference scriptAsBytes) {
// Scripts can be stored via API in several ways:
// 1) wrapped into a 'script' json object or field
// 2) wrapped into a 'template' json object or field
// 3) just as is
// In order to fetch the actual script in consistent manner this parsing logic is needed:
// EMPTY is ok here because we never call namedObject, we're just copying structure.
try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, scriptAsBytes);
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
parser.nextToken();
parser.nextToken();
if (parser.currentToken() == Token.END_OBJECT) {
throw new IllegalArgumentException("Empty script");
scripts.remove(lang + "#" + id);
return this;
}
switch (parser.currentName()) {
case "script":
case "template":
if (parser.nextToken() == Token.VALUE_STRING) {
return parser.text();
} else {
builder.copyCurrentStructure(parser);
/**
* @return A {@link ScriptMetaData} with the updated {@link Map} of scripts.
*/
public ScriptMetaData build() {
return new ScriptMetaData(scripts);
}
break;
default:
// There is no enclosing 'script' or 'template' object so we just need to return the script as is...
// because the parsers current location is already beyond the beginning we need to add a START_OBJECT:
builder.startObject();
builder.copyCurrentStructure(parser);
builder.endObject();
break;
}
return builder.string();
} catch (IOException e) {
throw new RuntimeException(e);
static final class ScriptMetadataDiff implements NamedDiff<MetaData.Custom> {
final Diff<Map<String, StoredScriptSource>> pipelines;
ScriptMetadataDiff(ScriptMetaData before, ScriptMetaData after) {
this.pipelines = DiffableUtils.diff(before.scripts, after.scripts, DiffableUtils.getStringKeySerializer());
}
ScriptMetadataDiff(StreamInput in) throws IOException {
pipelines = DiffableUtils.readJdkMapDiff(in, DiffableUtils.getStringKeySerializer(),
StoredScriptSource::new, StoredScriptSource::readDiffFrom);
}
@Override
@ -112,54 +167,243 @@ public final class ScriptMetaData implements MetaData.Custom {
return TYPE;
}
public static ScriptMetaData fromXContent(XContentParser parser) throws IOException {
Map<String, ScriptAsBytes> scripts = new HashMap<>();
String key = null;
for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
switch (token) {
case FIELD_NAME:
key = parser.currentName();
break;
case VALUE_STRING:
scripts.put(key, new ScriptAsBytes(new BytesArray(parser.text())));
break;
default:
throw new ParsingException(parser.getTokenLocation(), "Unexpected token [" + token + "]");
}
}
return new ScriptMetaData(scripts);
}
@Override
public EnumSet<MetaData.XContentContext> context() {
return MetaData.ALL_CONTEXTS;
}
public ScriptMetaData(StreamInput in) throws IOException {
int size = in.readVInt();
this.scripts = new HashMap<>();
for (int i = 0; i < size; i++) {
String languageAndId = in.readString();
BytesReference script = in.readBytesReference();
scripts.put(languageAndId, new ScriptAsBytes(script));
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
for (Map.Entry<String, ScriptAsBytes> entry : scripts.entrySet()) {
builder.field(entry.getKey(), entry.getValue().script.utf8ToString());
}
return builder;
public MetaData.Custom apply(MetaData.Custom part) {
return new ScriptMetaData(pipelines.apply(((ScriptMetaData) part).scripts));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
pipelines.writeTo(out);
}
}
/**
* Convenience method to build and return a new
* {@link ScriptMetaData} adding the specified stored script.
*/
static ScriptMetaData putStoredScript(ScriptMetaData previous, String id, StoredScriptSource source) {
Builder builder = new Builder(previous);
builder.storeScript(id, source);
return builder.build();
}
/**
* Convenience method to build and return a new
* {@link ScriptMetaData} deleting the specified stored script.
*/
static ScriptMetaData deleteStoredScript(ScriptMetaData previous, String id, String lang) {
Builder builder = new ScriptMetaData.Builder(previous);
builder.deleteScript(id, lang);
return builder.build();
}
/**
* Standard logger necessary for allocation of the deprecation logger.
*/
private static final Logger LOGGER = ESLoggerFactory.getLogger(ScriptMetaData.class);
/**
* Deprecation logger necessary for namespace changes related to stored scripts.
*/
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(LOGGER);
/**
* The type of {@link ClusterState} data.
*/
public static final String TYPE = "stored_scripts";
/**
* This will parse XContent into {@link ScriptMetaData}.
*
* The following format will be parsed for the new namespace:
*
* {@code
* {
* "<id>" : "<{@link StoredScriptSource#fromXContent(XContentParser)}>",
* "<id>" : "<{@link StoredScriptSource#fromXContent(XContentParser)}>",
* ...
* }
* }
*
* The following format will be parsed for the deprecated namespace:
*
* {@code
* {
* "<id>" : "<code>",
* "<id>" : "<code>",
* ...
* }
* }
*
* Note when using the deprecated namespace, the language will be pulled from
* the id and options will be set to an empty {@link Map}.
*/
public static ScriptMetaData fromXContent(XContentParser parser) throws IOException {
Map<String, StoredScriptSource> scripts = new HashMap<>();
String id = null;
StoredScriptSource source;
Token token = parser.currentToken();
if (token == null) {
token = parser.nextToken();
}
if (token != Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{]");
}
token = parser.nextToken();
while (token != Token.END_OBJECT) {
switch (token) {
case FIELD_NAME:
id = parser.currentName();
break;
case VALUE_STRING:
if (id == null) {
throw new ParsingException(parser.getTokenLocation(),
"unexpected token [" + token + "], expected [<id>, <code>, {]");
}
int split = id.indexOf('#');
if (split == -1) {
throw new IllegalArgumentException("illegal stored script id [" + id + "], does not contain lang");
} else {
source = new StoredScriptSource(id.substring(0, split), parser.text(), Collections.emptyMap());
}
scripts.put(id, source);
id = null;
break;
case START_OBJECT:
if (id == null) {
throw new ParsingException(parser.getTokenLocation(),
"unexpected token [" + token + "], expected [<id>, <code>, {]");
}
source = StoredScriptSource.fromXContent(parser);
scripts.put(id, source);
break;
default:
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [<id>, <code>, {]");
}
token = parser.nextToken();
}
return new ScriptMetaData(scripts);
}
public static NamedDiff<MetaData.Custom> readDiffFrom(StreamInput in) throws IOException {
return new ScriptMetadataDiff(in);
}
private final Map<String, StoredScriptSource> scripts;
/**
* Standard constructor to create metadata to store scripts.
* @param scripts The currently stored scripts. Must not be {@code null},
* use and empty {@link Map} to specify there were no
* previously stored scripts.
*/
ScriptMetaData(Map<String, StoredScriptSource> scripts) {
this.scripts = Collections.unmodifiableMap(scripts);
}
public ScriptMetaData(StreamInput in) throws IOException {
Map<String, StoredScriptSource> scripts = new HashMap<>();
StoredScriptSource source;
int size = in.readVInt();
for (int i = 0; i < size; i++) {
String id = in.readString();
// Prior to version 5.3 all scripts were stored using the deprecated namespace.
// Split the id to find the language then use StoredScriptSource to parse the
// expected BytesReference after which a new StoredScriptSource is created
// with the appropriate language and options.
if (in.getVersion().before(Version.V_5_3_0_UNRELEASED)) {
int split = id.indexOf('#');
if (split == -1) {
throw new IllegalArgumentException("illegal stored script id [" + id + "], does not contain lang");
} else {
source = new StoredScriptSource(in);
source = new StoredScriptSource(id.substring(0, split), source.getCode(), Collections.emptyMap());
}
// Version 5.3+ can just be parsed normally using StoredScriptSource.
} else {
source = new StoredScriptSource(in);
}
scripts.put(id, source);
}
this.scripts = Collections.unmodifiableMap(scripts);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
// Version 5.3+ will output the contents of the scripts' Map using
// StoredScriptSource to stored the language, code, and options.
if (out.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
out.writeVInt(scripts.size());
for (Map.Entry<String, ScriptAsBytes> entry : scripts.entrySet()) {
for (Map.Entry<String, StoredScriptSource> entry : scripts.entrySet()) {
out.writeString(entry.getKey());
entry.getValue().writeTo(out);
}
// Prior to Version 5.3, stored scripts can only be read using the deprecated
// namespace. Scripts using the deprecated namespace are first isolated in a
// temporary Map, then written out. Since all scripts will be stored using the
// deprecated namespace, no scripts will be lost.
} else {
Map<String, StoredScriptSource> filtered = new HashMap<>();
for (Map.Entry<String, StoredScriptSource> entry : scripts.entrySet()) {
if (entry.getKey().contains("#")) {
filtered.put(entry.getKey(), entry.getValue());
}
}
out.writeVInt(filtered.size());
for (Map.Entry<String, StoredScriptSource> entry : filtered.entrySet()) {
out.writeString(entry.getKey());
entry.getValue().writeTo(out);
}
}
}
/**
* This will write XContent from {@link ScriptMetaData}. The following format will be written:
*
* {@code
* {
* "<id>" : "<{@link StoredScriptSource#toXContent(XContentBuilder, Params)}>",
* "<id>" : "<{@link StoredScriptSource#toXContent(XContentBuilder, Params)}>",
* ...
* }
* }
*/
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
for (Map.Entry<String, StoredScriptSource> entry : scripts.entrySet()) {
builder.field(entry.getKey());
entry.getValue().toXContent(builder, params);
}
return builder;
}
@Override
@ -167,8 +411,27 @@ public final class ScriptMetaData implements MetaData.Custom {
return new ScriptMetadataDiff((ScriptMetaData)before, this);
}
public static NamedDiff<MetaData.Custom> readDiffFrom(StreamInput in) throws IOException {
return new ScriptMetadataDiff(in);
@Override
public String getWriteableName() {
return TYPE;
}
@Override
public EnumSet<MetaData.XContentContext> context() {
return MetaData.ALL_CONTEXTS;
}
/**
* Retrieves a stored script from the new namespace if lang is {@code null}.
* Otherwise, returns a stored script from the deprecated namespace. Either
* way an id is required.
*/
StoredScriptSource getStoredScript(String id, String lang) {
if (lang == null) {
return scripts.get(id);
} else {
return scripts.get(lang + "#" + id);
}
}
@Override
@ -176,8 +439,10 @@ public final class ScriptMetaData implements MetaData.Custom {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScriptMetaData other = (ScriptMetaData) o;
return scripts.equals(other.scripts);
ScriptMetaData that = (ScriptMetaData)o;
return scripts.equals(that.scripts);
}
@Override
@ -191,112 +456,4 @@ public final class ScriptMetaData implements MetaData.Custom {
"scripts=" + scripts +
'}';
}
static String toKey(String language, String id) {
if (id.contains("#")) {
throw new IllegalArgumentException("stored script id can't contain: '#'");
}
if (language.contains("#")) {
throw new IllegalArgumentException("stored script language can't contain: '#'");
}
return language + "#" + id;
}
public static final class Builder {
private Map<String, ScriptAsBytes> scripts;
public Builder(ScriptMetaData previous) {
if (previous != null) {
this.scripts = new HashMap<>(previous.scripts);
} else {
this.scripts = new HashMap<>();
}
}
public Builder storeScript(String lang, String id, BytesReference script) {
BytesReference scriptBytest = new BytesArray(parseStoredScript(script));
scripts.put(toKey(lang, id), new ScriptAsBytes(scriptBytest));
return this;
}
public Builder deleteScript(String lang, String id) {
if (scripts.remove(toKey(lang, id)) == null) {
throw new ResourceNotFoundException("Stored script with id [{}] for language [{}] does not exist", id, lang);
}
return this;
}
public ScriptMetaData build() {
return new ScriptMetaData(Collections.unmodifiableMap(scripts));
}
}
static final class ScriptMetadataDiff implements NamedDiff<MetaData.Custom> {
final Diff<Map<String, ScriptAsBytes>> pipelines;
ScriptMetadataDiff(ScriptMetaData before, ScriptMetaData after) {
this.pipelines = DiffableUtils.diff(before.scripts, after.scripts, DiffableUtils.getStringKeySerializer());
}
public ScriptMetadataDiff(StreamInput in) throws IOException {
pipelines = DiffableUtils.readJdkMapDiff(in, DiffableUtils.getStringKeySerializer(), ScriptAsBytes::new,
ScriptAsBytes::readDiffFrom);
}
@Override
public MetaData.Custom apply(MetaData.Custom part) {
return new ScriptMetaData(pipelines.apply(((ScriptMetaData) part).scripts));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
pipelines.writeTo(out);
}
@Override
public String getWriteableName() {
return TYPE;
}
}
static final class ScriptAsBytes extends AbstractDiffable<ScriptAsBytes> {
public ScriptAsBytes(BytesReference script) {
this.script = script;
}
private final BytesReference script;
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBytesReference(script);
}
public ScriptAsBytes(StreamInput in) throws IOException {
this(in.readBytesReference());
}
public static Diff<ScriptAsBytes> readDiffFrom(StreamInput in) throws IOException {
return readDiffFrom(ScriptAsBytes::new, in);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScriptAsBytes that = (ScriptAsBytes) o;
return script.equals(that.script);
}
@Override
public int hashCode() {
return script.hashCode();
}
}
}

View File

@ -38,7 +38,6 @@ import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.cache.RemovalListener;
@ -46,7 +45,6 @@ import org.elasticsearch.common.cache.RemovalNotification;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
@ -209,18 +207,44 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
/**
* Checks if a script can be executed and compiles it if needed, or returns the previously compiled and cached script.
*/
public CompiledScript compile(Script script, ScriptContext scriptContext, Map<String, String> params) {
if (script == null) {
throw new IllegalArgumentException("The parameter script (Script) must not be null.");
}
if (scriptContext == null) {
throw new IllegalArgumentException("The parameter scriptContext (ScriptContext) must not be null.");
public CompiledScript compile(Script script, ScriptContext scriptContext) {
Objects.requireNonNull(script);
Objects.requireNonNull(scriptContext);
ScriptType type = script.getType();
String lang = script.getLang();
String idOrCode = script.getIdOrCode();
Map<String, String> options = script.getOptions();
String id = idOrCode;
// lang may be null when looking up a stored script, so we must get the
// source to retrieve the lang before checking if the context is supported
if (type == ScriptType.STORED) {
// search template requests can possibly pass in the entire path instead
// of just an id for looking up a stored script, so we parse the path and
// check for appropriate errors
String[] path = id.split("/");
if (path.length == 3) {
if (lang != null && lang.equals(path[1]) == false) {
throw new IllegalStateException("conflicting script languages, found [" + path[1] + "] but expected [" + lang + "]");
}
String lang = script.getLang();
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(lang);
if (canExecuteScript(lang, script.getType(), scriptContext) == false) {
throw new IllegalStateException("scripts of type [" + script.getType() + "], operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are disabled");
id = path[2];
deprecationLogger.deprecated("use of </lang/id> [" + idOrCode + "] for looking up" +
" stored scripts/templates has been deprecated, use only <id> [" + id + "] instead");
} else if (path.length != 1) {
throw new IllegalArgumentException("illegal stored script format [" + id + "] use only <id>");
}
// a stored script must be pulled from the cluster state every time in case
// the script has been updated since the last compilation
StoredScriptSource source = getScriptFromClusterState(id, lang);
lang = source.getLang();
idOrCode = source.getCode();
options = source.getOptions();
}
// TODO: fix this through some API or something, that's wrong
@ -232,7 +256,68 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
" operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are not supported");
}
return compileInternal(script, params);
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(lang);
if (canExecuteScript(lang, type, scriptContext) == false) {
throw new IllegalStateException("scripts of type [" + script.getType() + "]," +
" operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are disabled");
}
if (logger.isTraceEnabled()) {
logger.trace("compiling lang: [{}] type: [{}] script: {}", lang, type, idOrCode);
}
if (type == ScriptType.FILE) {
CacheKey cacheKey = new CacheKey(lang, idOrCode, options);
CompiledScript compiledScript = staticCache.get(cacheKey);
if (compiledScript == null) {
throw new IllegalArgumentException("unable to find file script [" + idOrCode + "] using lang [" + lang + "]");
}
return compiledScript;
}
CacheKey cacheKey = new CacheKey(lang, idOrCode, options);
CompiledScript compiledScript = cache.get(cacheKey);
if (compiledScript != null) {
return compiledScript;
}
// Synchronize so we don't compile scripts many times during multiple shards all compiling a script
synchronized (this) {
// Retrieve it again in case it has been put by a different thread
compiledScript = cache.get(cacheKey);
if (compiledScript == null) {
try {
// Either an un-cached inline script or indexed script
// If the script type is inline the name will be the same as the code for identification in exceptions
// but give the script engine the chance to be better, give it separate name + source code
// for the inline case, then its anonymous: null.
if (logger.isTraceEnabled()) {
logger.trace("compiling script, type: [{}], lang: [{}], options: [{}]", type, lang, options);
}
// Check whether too many compilations have happened
checkCompilationLimit();
compiledScript = new CompiledScript(type, id, lang, scriptEngineService.compile(id, idOrCode, options));
} catch (ScriptException good) {
// TODO: remove this try-catch completely, when all script engines have good exceptions!
throw good; // its already good
} catch (Exception exception) {
throw new GeneralScriptException("Failed to compile " + type + " script [" + id + "] using lang [" + lang + "]", exception);
}
// Since the cache key is the script content itself we don't need to
// invalidate/check the cache if an indexed script changes.
scriptMetrics.onCompilation();
cache.put(cacheKey, compiledScript);
}
return compiledScript;
}
}
/**
@ -267,152 +352,71 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
}
}
/**
* Compiles a script straight-away, or returns the previously compiled and cached script,
* without checking if it can be executed based on settings.
*/
CompiledScript compileInternal(Script script, Map<String, String> params) {
if (script == null) {
throw new IllegalArgumentException("The parameter script (Script) must not be null.");
public boolean isLangSupported(String lang) {
Objects.requireNonNull(lang);
return scriptEnginesByLang.containsKey(lang);
}
String lang = script.getLang();
ScriptType type = script.getType();
//script.getIdOrCode() could return either a name or code for a script,
//but we check for a file script name first and an indexed script name second
String name = script.getIdOrCode();
if (logger.isTraceEnabled()) {
logger.trace("Compiling lang: [{}] type: [{}] script: {}", lang, type, name);
StoredScriptSource getScriptFromClusterState(String id, String lang) {
if (lang != null && isLangSupported(lang) == false) {
throw new IllegalArgumentException("unable to get stored script with unsupported lang [" + lang + "]");
}
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(lang);
if (type == ScriptType.FILE) {
CacheKey cacheKey = new CacheKey(scriptEngineService, name, null, params);
//On disk scripts will be loaded into the staticCache by the listener
CompiledScript compiledScript = staticCache.get(cacheKey);
if (compiledScript == null) {
throw new IllegalArgumentException("Unable to find on disk file script [" + name + "] using lang [" + lang + "]");
}
return compiledScript;
}
//script.getIdOrCode() will be code if the script type is inline
String code = script.getIdOrCode();
if (type == ScriptType.STORED) {
//The look up for an indexed script must be done every time in case
//the script has been updated in the index since the last look up.
final IndexedScript indexedScript = new IndexedScript(lang, name);
name = indexedScript.id;
code = getScriptFromClusterState(indexedScript.lang, indexedScript.id);
}
CacheKey cacheKey = new CacheKey(scriptEngineService, type == ScriptType.INLINE ? null : name, code, params);
CompiledScript compiledScript = cache.get(cacheKey);
if (compiledScript != null) {
return compiledScript;
}
// Synchronize so we don't compile scripts many times during multiple shards all compiling a script
synchronized (this) {
// Retrieve it again in case it has been put by a different thread
compiledScript = cache.get(cacheKey);
if (compiledScript == null) {
try {
// Either an un-cached inline script or indexed script
// If the script type is inline the name will be the same as the code for identification in exceptions
// but give the script engine the chance to be better, give it separate name + source code
// for the inline case, then its anonymous: null.
String actualName = (type == ScriptType.INLINE) ? null : name;
if (logger.isTraceEnabled()) {
logger.trace("compiling script, type: [{}], lang: [{}], params: [{}]", type, lang, params);
}
// Check whether too many compilations have happened
checkCompilationLimit();
compiledScript = new CompiledScript(type, name, lang, scriptEngineService.compile(actualName, code, params));
} catch (ScriptException good) {
// TODO: remove this try-catch completely, when all script engines have good exceptions!
throw good; // its already good
} catch (Exception exception) {
throw new GeneralScriptException("Failed to compile " + type + " script [" + name + "] using lang [" + lang + "]", exception);
}
// Since the cache key is the script content itself we don't need to
// invalidate/check the cache if an indexed script changes.
scriptMetrics.onCompilation();
cache.put(cacheKey, compiledScript);
}
return compiledScript;
}
}
private String validateScriptLanguage(String scriptLang) {
Objects.requireNonNull(scriptLang);
if (scriptEnginesByLang.containsKey(scriptLang) == false) {
throw new IllegalArgumentException("script_lang not supported [" + scriptLang + "]");
}
return scriptLang;
}
String getScriptFromClusterState(String scriptLang, String id) {
scriptLang = validateScriptLanguage(scriptLang);
ScriptMetaData scriptMetadata = clusterState.metaData().custom(ScriptMetaData.TYPE);
if (scriptMetadata == null) {
throw new ResourceNotFoundException("Unable to find script [" + scriptLang + "/" + id + "] in cluster state");
throw new ResourceNotFoundException("unable to find script [" + id + "]" +
(lang == null ? "" : " using lang [" + lang + "]") + " in cluster state");
}
String script = scriptMetadata.getScript(scriptLang, id);
if (script == null) {
throw new ResourceNotFoundException("Unable to find script [" + scriptLang + "/" + id + "] in cluster state");
}
return script;
StoredScriptSource source = scriptMetadata.getStoredScript(id, lang);
if (source == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "]" +
(lang == null ? "" : " using lang [" + lang + "]") + " in cluster state");
}
return source;
}
public void putStoredScript(ClusterService clusterService, PutStoredScriptRequest request,
ActionListener<PutStoredScriptResponse> listener) {
int max = SCRIPT_MAX_SIZE_IN_BYTES.get(settings);
if (request.content().length() > max) {
throw new IllegalArgumentException("exceeded max allowed stored script size in bytes [" + max + "] with size [" +
request.content().length() + "] for script [" + request.id() + "]");
}
StoredScriptSource source = StoredScriptSource.parse(request.lang(), request.content());
if (isLangSupported(source.getLang()) == false) {
throw new IllegalArgumentException("unable to put stored script with unsupported lang [" + source.getLang() + "]");
}
void validateStoredScript(String id, String scriptLang, BytesReference scriptBytes) {
validateScriptSize(id, scriptBytes.length());
String script = ScriptMetaData.parseStoredScript(scriptBytes);
if (Strings.hasLength(scriptBytes)) {
//Just try and compile it
try {
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(scriptLang);
//we don't know yet what the script will be used for, but if all of the operations for this lang with
//indexed scripts are disabled, it makes no sense to even compile it.
if (isAnyScriptContextEnabled(scriptLang, ScriptType.STORED)) {
Object compiled = scriptEngineService.compile(id, script, Collections.emptyMap());
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(source.getLang());
if (isAnyScriptContextEnabled(source.getLang(), ScriptType.STORED)) {
Object compiled = scriptEngineService.compile(request.id(), source.getCode(), Collections.emptyMap());
if (compiled == null) {
throw new IllegalArgumentException("Unable to parse [" + script + "] lang [" + scriptLang +
"] (ScriptService.compile returned null)");
throw new IllegalArgumentException("failed to parse/compile stored script [" + request.id() + "]" +
(source.getCode() == null ? "" : " using code [" + source.getCode() + "]"));
}
} else {
logger.warn(
"skipping compile of script [{}], lang [{}] as all scripted operations are disabled for indexed scripts",
script, scriptLang);
throw new IllegalArgumentException(
"cannot put stored script [" + request.id() + "], stored scripts cannot be run under any context");
}
} catch (ScriptException good) {
// TODO: remove this when all script engines have good exceptions!
throw good; // its already good!
} catch (Exception e) {
throw new IllegalArgumentException("Unable to parse [" + script +
"] lang [" + scriptLang + "]", e);
}
} else {
throw new IllegalArgumentException("Unable to find script in : " + scriptBytes.utf8ToString());
}
throw good;
} catch (Exception exception) {
throw new IllegalArgumentException("failed to parse/compile stored script [" + request.id() + "]", exception);
}
public void storeScript(ClusterService clusterService, PutStoredScriptRequest request, ActionListener<PutStoredScriptResponse> listener) {
String scriptLang = validateScriptLanguage(request.scriptLang());
//verify that the script compiles
validateStoredScript(request.id(), scriptLang, request.script());
clusterService.submitStateUpdateTask("put-script-" + request.id(), new AckedClusterStateUpdateTask<PutStoredScriptResponse>(request, listener) {
clusterService.submitStateUpdateTask("put-script-" + request.id(),
new AckedClusterStateUpdateTask<PutStoredScriptResponse>(request, listener) {
@Override
protected PutStoredScriptResponse newResponse(boolean acknowledged) {
@ -421,23 +425,23 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
return innerStoreScript(currentState, scriptLang, request);
ScriptMetaData smd = currentState.metaData().custom(ScriptMetaData.TYPE);
smd = ScriptMetaData.putStoredScript(smd, request.id(), source);
MetaData.Builder mdb = MetaData.builder(currentState.getMetaData()).putCustom(ScriptMetaData.TYPE, smd);
return ClusterState.builder(currentState).metaData(mdb).build();
}
});
}
static ClusterState innerStoreScript(ClusterState currentState, String validatedScriptLang, PutStoredScriptRequest request) {
ScriptMetaData scriptMetadata = currentState.metaData().custom(ScriptMetaData.TYPE);
ScriptMetaData.Builder scriptMetadataBuilder = new ScriptMetaData.Builder(scriptMetadata);
scriptMetadataBuilder.storeScript(validatedScriptLang, request.id(), request.script());
MetaData.Builder metaDataBuilder = MetaData.builder(currentState.getMetaData())
.putCustom(ScriptMetaData.TYPE, scriptMetadataBuilder.build());
return ClusterState.builder(currentState).metaData(metaDataBuilder).build();
public void deleteStoredScript(ClusterService clusterService, DeleteStoredScriptRequest request,
ActionListener<DeleteStoredScriptResponse> listener) {
if (request.lang() != null && isLangSupported(request.lang()) == false) {
throw new IllegalArgumentException("unable to delete stored script with unsupported lang [" + request.lang() +"]");
}
public void deleteStoredScript(ClusterService clusterService, DeleteStoredScriptRequest request, ActionListener<DeleteStoredScriptResponse> listener) {
String scriptLang = validateScriptLanguage(request.scriptLang());
clusterService.submitStateUpdateTask("delete-script-" + request.id(), new AckedClusterStateUpdateTask<DeleteStoredScriptResponse>(request, listener) {
clusterService.submitStateUpdateTask("delete-script-" + request.id(),
new AckedClusterStateUpdateTask<DeleteStoredScriptResponse>(request, listener) {
@Override
protected DeleteStoredScriptResponse newResponse(boolean acknowledged) {
@ -446,24 +450,20 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
return innerDeleteScript(currentState, scriptLang, request);
ScriptMetaData smd = currentState.metaData().custom(ScriptMetaData.TYPE);
smd = ScriptMetaData.deleteStoredScript(smd, request.id(), request.lang());
MetaData.Builder mdb = MetaData.builder(currentState.getMetaData()).putCustom(ScriptMetaData.TYPE, smd);
return ClusterState.builder(currentState).metaData(mdb).build();
}
});
}
static ClusterState innerDeleteScript(ClusterState currentState, String validatedLang, DeleteStoredScriptRequest request) {
ScriptMetaData scriptMetadata = currentState.metaData().custom(ScriptMetaData.TYPE);
ScriptMetaData.Builder scriptMetadataBuilder = new ScriptMetaData.Builder(scriptMetadata);
scriptMetadataBuilder.deleteScript(validatedLang, request.id());
MetaData.Builder metaDataBuilder = MetaData.builder(currentState.getMetaData())
.putCustom(ScriptMetaData.TYPE, scriptMetadataBuilder.build());
return ClusterState.builder(currentState).metaData(metaDataBuilder).build();
}
public String getStoredScript(ClusterState state, GetStoredScriptRequest request) {
public StoredScriptSource getStoredScript(ClusterState state, GetStoredScriptRequest request) {
ScriptMetaData scriptMetadata = state.metaData().custom(ScriptMetaData.TYPE);
if (scriptMetadata != null) {
return scriptMetadata.getScript(request.lang(), request.id());
return scriptMetadata.getStoredScript(request.id(), request.lang());
} else {
return null;
}
@ -473,7 +473,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
* Compiles (or retrieves from cache) and executes the provided script
*/
public ExecutableScript executable(Script script, ScriptContext scriptContext) {
return executable(compile(script, scriptContext, script.getOptions()), script.getParams());
return executable(compile(script, scriptContext), script.getParams());
}
/**
@ -487,7 +487,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
* Compiles (or retrieves from cache) and executes the provided search script
*/
public SearchScript search(SearchLookup lookup, Script script, ScriptContext scriptContext) {
CompiledScript compiledScript = compile(script, scriptContext, script.getOptions());
CompiledScript compiledScript = compile(script, scriptContext);
return search(lookup, compiledScript, script.getParams());
}
@ -520,18 +520,6 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
return scriptMetrics.stats();
}
private void validateScriptSize(String identifier, int scriptSizeInBytes) {
int allowedScriptSizeInBytes = SCRIPT_MAX_SIZE_IN_BYTES.get(settings);
if (scriptSizeInBytes > allowedScriptSizeInBytes) {
String message = LoggerMessageFormat.format(
"Limit of script size in bytes [{}] has been exceeded for script [{}] with size [{}]",
allowedScriptSizeInBytes,
identifier,
scriptSizeInBytes);
throw new IllegalArgumentException(message);
}
}
@Override
public void clusterChanged(ClusterChangedEvent event) {
clusterState = event.state();
@ -592,11 +580,11 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
logger.info("compiling script file [{}]", file.toAbsolutePath());
try (InputStreamReader reader = new InputStreamReader(Files.newInputStream(file), StandardCharsets.UTF_8)) {
String script = Streams.copyToString(reader);
String name = scriptNameExt.v1();
CacheKey cacheKey = new CacheKey(engineService, name, null, Collections.emptyMap());
String id = scriptNameExt.v1();
CacheKey cacheKey = new CacheKey(engineService.getType(), id, null);
// pass the actual file name to the compiler (for script engines that care about this)
Object executable = engineService.compile(file.getFileName().toString(), script, Collections.emptyMap());
CompiledScript compiledScript = new CompiledScript(ScriptType.FILE, name, engineService.getType(), executable);
CompiledScript compiledScript = new CompiledScript(ScriptType.FILE, id, engineService.getType(), executable);
staticCache.put(cacheKey, compiledScript);
scriptMetrics.onCompilation();
}
@ -637,7 +625,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
ScriptEngineService engineService = getScriptEngineServiceForFileExt(scriptNameExt.v2());
assert engineService != null;
logger.info("removing script file [{}]", file.toAbsolutePath());
staticCache.remove(new CacheKey(engineService, scriptNameExt.v1(), null, Collections.emptyMap()));
staticCache.remove(new CacheKey(engineService.getType(), scriptNameExt.v1(), null));
}
}
@ -650,15 +638,13 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
private static final class CacheKey {
final String lang;
final String name;
final String code;
final Map<String, String> params;
final String idOrCode;
final Map<String, String> options;
private CacheKey(final ScriptEngineService service, final String name, final String code, final Map<String, String> params) {
this.lang = service.getType();
this.name = name;
this.code = code;
this.params = params;
private CacheKey(String lang, String idOrCode, Map<String, String> options) {
this.lang = lang;
this.idOrCode = idOrCode;
this.options = options;
}
@Override
@ -668,44 +654,18 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
CacheKey cacheKey = (CacheKey)o;
if (!lang.equals(cacheKey.lang)) return false;
if (name != null ? !name.equals(cacheKey.name) : cacheKey.name != null) return false;
if (code != null ? !code.equals(cacheKey.code) : cacheKey.code != null) return false;
return params.equals(cacheKey.params);
if (lang != null ? !lang.equals(cacheKey.lang) : cacheKey.lang != null) return false;
if (!idOrCode.equals(cacheKey.idOrCode)) return false;
return options != null ? options.equals(cacheKey.options) : cacheKey.options == null;
}
@Override
public int hashCode() {
int result = lang.hashCode();
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (code != null ? code.hashCode() : 0);
result = 31 * result + params.hashCode();
int result = lang != null ? lang.hashCode() : 0;
result = 31 * result + idOrCode.hashCode();
result = 31 * result + (options != null ? options.hashCode() : 0);
return result;
}
}
private static class IndexedScript {
private final String lang;
private final String id;
IndexedScript(String lang, String script) {
this.lang = lang;
final String[] parts = script.split("/");
if (parts.length == 1) {
this.id = script;
} else {
if (parts.length != 3) {
throw new IllegalArgumentException("Illegal index script format [" + script + "]" +
" should be /lang/id");
} else {
if (!parts[1].equals(this.lang)) {
throw new IllegalStateException("Conflicting script language, found [" + parts[1] + "] expected + ["+ this.lang + "]");
}
this.id = parts[2];
}
}
}
}
}

View File

@ -0,0 +1,485 @@
/*
* 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.Version;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
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.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* {@link StoredScriptSource} represents user-defined parameters for a script
* saved in the {@link ClusterState}.
*/
public class StoredScriptSource extends AbstractDiffable<StoredScriptSource> implements Writeable, ToXContent {
/**
* Standard {@link ParseField} for outer level of stored script source.
*/
public static final ParseField SCRIPT_PARSE_FIELD = new ParseField("script");
/**
* Standard {@link ParseField} for outer level of stored script source.
*/
public static final ParseField TEMPLATE_PARSE_FIELD = new ParseField("template");
/**
* Standard {@link ParseField} for lang on the inner level.
*/
public static final ParseField LANG_PARSE_FIELD = new ParseField("lang");
/**
* Standard {@link ParseField} for code on the inner level.
*/
public static final ParseField CODE_PARSE_FIELD = new ParseField("code");
/**
* Standard {@link ParseField} for options on the inner level.
*/
public static final ParseField OPTIONS_PARSE_FIELD = new ParseField("options");
/**
* Helper class used by {@link ObjectParser} to store mutable {@link StoredScriptSource} variables and then
* construct an immutable {@link StoredScriptSource} object based on parsed XContent.
*/
private static final class Builder {
private String lang;
private String code;
private Map<String, String> options;
private Builder() {
// This cannot default to an empty map because options are potentially added at multiple points.
this.options = new HashMap<>();
}
private void setLang(String lang) {
this.lang = lang;
}
/**
* Since stored scripts can accept templates rather than just scripts, they must also be able
* to handle template parsing, hence the need for custom parsing code. Templates can
* consist of either an {@link String} or a JSON object. If a JSON object is discovered
* then the content type option must also be saved as a compiler option.
*/
private void setCode(XContentParser parser) {
try {
if (parser.currentToken() == Token.START_OBJECT) {
XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType());
code = builder.copyCurrentStructure(parser).bytes().utf8ToString();
options.put(Script.CONTENT_TYPE_OPTION, parser.contentType().mediaType());
} else {
code = parser.text();
}
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}
/**
* Options may have already been added if a template was specified.
* Appends the user-defined compiler options with the internal compiler options.
*/
private void setOptions(Map<String, String> options) {
if (options.containsKey(Script.CONTENT_TYPE_OPTION)) {
throw new IllegalArgumentException(Script.CONTENT_TYPE_OPTION + " cannot be user-specified");
}
this.options.putAll(options);
}
/**
* Validates the parameters and creates an {@link StoredScriptSource}.
*/
private StoredScriptSource build() {
if (lang == null) {
throw new IllegalArgumentException("must specify lang for stored script");
} else if (lang.isEmpty()) {
throw new IllegalArgumentException("lang cannot be empty");
}
if (code == null) {
throw new IllegalArgumentException("must specify code for stored script");
} else if (code.isEmpty()) {
throw new IllegalArgumentException("code cannot be empty");
}
if (options.size() > 1 || options.size() == 1 && options.get(Script.CONTENT_TYPE_OPTION) == null) {
throw new IllegalArgumentException("illegal compiler options [" + options + "] specified");
}
return new StoredScriptSource(lang, code, options);
}
}
private static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("stored script source", Builder::new);
static {
// Defines the fields necessary to parse a Script as XContent using an ObjectParser.
PARSER.declareString(Builder::setLang, LANG_PARSE_FIELD);
PARSER.declareField(Builder::setCode, parser -> parser, CODE_PARSE_FIELD, ValueType.OBJECT_OR_STRING);
PARSER.declareField(Builder::setOptions, XContentParser::mapStrings, OPTIONS_PARSE_FIELD, ValueType.OBJECT);
}
/**
* This will parse XContent into a {@link StoredScriptSource}. The following formats can be parsed:
*
* The simple script format with no compiler options or user-defined params:
*
* Example:
* {@code
* {"script": "return Math.log(doc.popularity) * 100;"}
* }
*
* The above format requires the lang to be specified using the deprecated stored script namespace
* (as a url parameter during a put request). See {@link ScriptMetaData} for more information about
* the stored script namespaces.
*
* The complex script format using the new stored script namespace
* where lang and code are required but options is optional:
*
* {@code
* {
* "script" : {
* "lang" : "<lang>",
* "code" : "<code>",
* "options" : {
* "option0" : "<option0>",
* "option1" : "<option1>",
* ...
* }
* }
* }
* }
*
* Example:
* {@code
* {
* "script": {
* "lang" : "painless",
* "code" : "return Math.log(doc.popularity) * params.multiplier"
* }
* }
* }
*
* The simple template format:
*
* {@code
* {
* "query" : ...
* }
* }
*
* The complex template format:
*
* {@code
* {
* "template": {
* "query" : ...
* }
* }
* }
*
* Note that templates can be handled as both strings and complex JSON objects.
* Also templates may be part of the 'code' parameter in a script. The Parser
* can handle this case as well.
*
* @param lang An optional parameter to allow for use of the deprecated stored
* script namespace. This will be used to specify the language
* coming in as a url parameter from a request or for stored templates.
* @param content The content from the request to be parsed as described above.
* @return The parsed {@link StoredScriptSource}.
*/
public static StoredScriptSource parse(String lang, BytesReference content) {
try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, content)) {
Token token = parser.nextToken();
if (token != Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{]");
}
token = parser.nextToken();
if (token != Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + ", expected [" +
SCRIPT_PARSE_FIELD.getPreferredName() + ", " + TEMPLATE_PARSE_FIELD.getPreferredName());
}
String name = parser.currentName();
if (SCRIPT_PARSE_FIELD.getPreferredName().equals(name)) {
token = parser.nextToken();
if (token == Token.VALUE_STRING) {
if (lang == null) {
throw new IllegalArgumentException(
"must specify lang as a url parameter when using the deprecated stored script namespace");
}
return new StoredScriptSource(lang, parser.text(), Collections.emptyMap());
} else if (token == Token.START_OBJECT) {
if (lang == null) {
return PARSER.apply(parser, null).build();
} else {
try (XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType())) {
builder.copyCurrentStructure(parser);
return new StoredScriptSource(lang, builder.string(),
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, parser.contentType().mediaType()));
}
}
} else {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{, <code>]");
}
} else {
if (lang == null) {
throw new IllegalArgumentException("unexpected stored script format");
}
if (TEMPLATE_PARSE_FIELD.getPreferredName().equals(name)) {
token = parser.nextToken();
if (token == Token.VALUE_STRING) {
return new StoredScriptSource(lang, parser.text(),
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, parser.contentType().mediaType()));
}
}
try (XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType())) {
if (token != Token.START_OBJECT) {
builder.startObject();
builder.copyCurrentStructure(parser);
builder.endObject();
} else {
builder.copyCurrentStructure(parser);
}
return new StoredScriptSource(lang, builder.string(),
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, parser.contentType().mediaType()));
}
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
/**
* This will parse XContent into a {@link StoredScriptSource}. The following format is what will be parsed:
*
* {@code
* {
* "script" : {
* "lang" : "<lang>",
* "code" : "<code>",
* "options" : {
* "option0" : "<option0>",
* "option1" : "<option1>",
* ...
* }
* }
* }
* }
*
* Note that the "code" parameter can also handle template parsing including from
* a complex JSON object.
*/
public static StoredScriptSource fromXContent(XContentParser parser) throws IOException {
return PARSER.apply(parser, null).build();
}
/**
* Required for {@link ScriptMetaData.ScriptMetadataDiff}. Uses
* the {@link StoredScriptSource#StoredScriptSource(StreamInput)}
* constructor.
*/
public static Diff<StoredScriptSource> readDiffFrom(StreamInput in) throws IOException {
return readDiffFrom(StoredScriptSource::new, in);
}
private final String lang;
private final String code;
private final Map<String, String> options;
/**
* Constructor for use with {@link GetStoredScriptResponse}
* to support the deprecated stored script namespace.
*/
public StoredScriptSource(String code) {
this.lang = null;
this.code = Objects.requireNonNull(code);
this.options = null;
}
/**
* Standard StoredScriptSource constructor.
* @param lang The language to compile the script with. Must not be {@code null}.
* @param code The source code to compile with. Must not be {@code null}.
* @param options Compiler options to be compiled with. Must not be {@code null},
* use an empty {@link Map} to represent no options.
*/
public StoredScriptSource(String lang, String code, Map<String, String> options) {
this.lang = Objects.requireNonNull(lang);
this.code = Objects.requireNonNull(code);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
}
/**
* Reads a {@link StoredScriptSource} from a stream. Version 5.3+ will read
* all of the lang, code, and options parameters. For versions prior to 5.3,
* only the code parameter will be read in as a bytes reference.
*/
public StoredScriptSource(StreamInput in) throws IOException {
if (in.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
this.lang = in.readString();
this.code = in.readString();
@SuppressWarnings("unchecked")
Map<String, String> options = (Map<String, String>)(Map)in.readMap();
this.options = options;
} else {
this.lang = null;
this.code = in.readBytesReference().utf8ToString();
this.options = null;
}
}
/**
* Writes a {@link StoredScriptSource} to a stream. Version 5.3+ will write
* all of the lang, code, and options parameters. For versions prior to 5.3,
* only the code parameter will be read in as a bytes reference.
*/
@Override
public void writeTo(StreamOutput out) throws IOException {
if (out.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
out.writeString(lang);
out.writeString(code);
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>)(Map)this.options;
out.writeMap(options);
} else {
out.writeBytesReference(new BytesArray(code));
}
}
/**
* This will write XContent from a {@link StoredScriptSource}. The following format will be written:
*
* {@code
* {
* "script" : {
* "lang" : "<lang>",
* "code" : "<code>",
* "options" : {
* "option0" : "<option0>",
* "option1" : "<option1>",
* ...
* }
* }
* }
* }
*
* Note that the 'code' parameter can also handle templates written as complex JSON.
*/
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(LANG_PARSE_FIELD.getPreferredName(), lang);
builder.field(CODE_PARSE_FIELD.getPreferredName(), code);
builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options);
builder.endObject();
return builder;
}
/**
* @return The language used for compiling this script.
*/
public String getLang() {
return lang;
}
/**
* @return The code used for compiling this script.
*/
public String getCode() {
return code;
}
/**
* @return The compiler options used for this script.
*/
public Map<String, String> getOptions() {
return options;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoredScriptSource that = (StoredScriptSource)o;
if (lang != null ? !lang.equals(that.lang) : that.lang != null) return false;
if (code != null ? !code.equals(that.code) : that.code != null) return false;
return options != null ? options.equals(that.options) : that.options == null;
}
@Override
public int hashCode() {
int result = lang != null ? lang.hashCode() : 0;
result = 31 * result + (code != null ? code.hashCode() : 0);
result = 31 * result + (options != null ? options.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "StoredScriptSource{" +
"lang='" + lang + '\'' +
", code='" + code + '\'' +
", options=" + options +
'}';
}
}

View File

@ -88,8 +88,8 @@ public class InternalScriptedMetric extends InternalMetricsAggregation implement
if (firstAggregation.reduceScript.getParams() != null) {
vars.putAll(firstAggregation.reduceScript.getParams());
}
CompiledScript compiledScript = reduceContext.scriptService().compile(firstAggregation.reduceScript,
ScriptContext.Standard.AGGS, Collections.emptyMap());
CompiledScript compiledScript = reduceContext.scriptService().compile(
firstAggregation.reduceScript, ScriptContext.Standard.AGGS);
ExecutableScript script = reduceContext.scriptService().executable(compiledScript, vars);
aggregation = script.run();
} else {

View File

@ -92,8 +92,7 @@ public class BucketScriptPipelineAggregator extends PipelineAggregator {
InternalMultiBucketAggregation<InternalMultiBucketAggregation, InternalMultiBucketAggregation.InternalBucket> originalAgg = (InternalMultiBucketAggregation<InternalMultiBucketAggregation, InternalMultiBucketAggregation.InternalBucket>) aggregation;
List<? extends Bucket> buckets = originalAgg.getBuckets();
CompiledScript compiledScript = reduceContext.scriptService().compile(script, ScriptContext.Standard.AGGS,
Collections.emptyMap());
CompiledScript compiledScript = reduceContext.scriptService().compile(script, ScriptContext.Standard.AGGS);
List newBuckets = new ArrayList<>();
for (Bucket bucket : buckets) {
Map<String, Object> vars = new HashMap<>();

View File

@ -86,8 +86,7 @@ public class BucketSelectorPipelineAggregator extends PipelineAggregator {
(InternalMultiBucketAggregation<InternalMultiBucketAggregation, InternalMultiBucketAggregation.InternalBucket>) aggregation;
List<? extends Bucket> buckets = originalAgg.getBuckets();
CompiledScript compiledScript = reduceContext.scriptService().compile(script, ScriptContext.Standard.AGGS,
Collections.emptyMap());
CompiledScript compiledScript = reduceContext.scriptService().compile(script, ScriptContext.Standard.AGGS);
List newBuckets = new ArrayList<>();
for (Bucket bucket : buckets) {
Map<String, Object> vars = new HashMap<>();

View File

@ -415,7 +415,8 @@ public class InnerHitBuilderTests extends ESTestCase {
randomMap.put(String.valueOf(i), randomAsciiOfLength(16));
}
}
Script script = new Script(randomScriptType, randomAsciiOfLengthBetween(1, 4), randomAsciiOfLength(128), randomMap);
Script script = new Script(randomScriptType, randomScriptType == ScriptType.STORED ? null : randomAsciiOfLengthBetween(1, 4),
randomAsciiOfLength(128), randomMap);
return new SearchSourceBuilder.ScriptField(randomAsciiOfLengthBetween(1, 32), script, randomBoolean());
}

View File

@ -55,7 +55,7 @@ public class FileScriptTests extends ESTestCase {
.put("script.engine." + MockScriptEngine.NAME + ".file.aggs", "false").build();
ScriptService scriptService = makeScriptService(settings);
Script script = new Script(ScriptType.FILE, MockScriptEngine.NAME, "script1", Collections.emptyMap());
CompiledScript compiledScript = scriptService.compile(script, ScriptContext.Standard.SEARCH, Collections.emptyMap());
CompiledScript compiledScript = scriptService.compile(script, ScriptContext.Standard.SEARCH);
assertNotNull(compiledScript);
MockCompiledScript executable = (MockCompiledScript) compiledScript.compiled();
assertEquals("script1.mockscript", executable.getName());
@ -72,7 +72,7 @@ public class FileScriptTests extends ESTestCase {
Script script = new Script(ScriptType.FILE, MockScriptEngine.NAME, "script1", Collections.emptyMap());
for (ScriptContext context : ScriptContext.Standard.values()) {
try {
scriptService.compile(script, context, Collections.emptyMap());
scriptService.compile(script, context);
fail(context.getKey() + " script should have been rejected");
} catch(Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("scripts of type [file], operation [" + context.getKey() + "] and lang [" + MockScriptEngine.NAME + "] are disabled"));

View File

@ -80,7 +80,7 @@ public class NativeScriptTests extends ESTestCase {
for (ScriptContext scriptContext : scriptContextRegistry.scriptContexts()) {
assertThat(scriptService.compile(new Script(ScriptType.INLINE, NativeScriptEngineService.NAME, "my", Collections.emptyMap()),
scriptContext, Collections.emptyMap()), notNullValue());
scriptContext), notNullValue());
}
}

View File

@ -19,6 +19,10 @@
package org.elasticsearch.script;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
@ -50,8 +54,16 @@ public class ScriptContextTests extends ESTestCase {
new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op"));
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(customContexts);
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
ScriptService scriptService = new ScriptService(settings, new Environment(settings), null, scriptEngineRegistry, scriptContextRegistry, scriptSettings);
return new ScriptService(settings, new Environment(settings), null, scriptEngineRegistry, scriptContextRegistry, scriptSettings);
ClusterState empty = ClusterState.builder(new ClusterName("_name")).build();
ScriptMetaData smd = empty.metaData().custom(ScriptMetaData.TYPE);
smd = ScriptMetaData.putStoredScript(smd, "1", new StoredScriptSource(MockScriptEngine.NAME, "1", Collections.emptyMap()));
MetaData.Builder mdb = MetaData.builder(empty.getMetaData()).putCustom(ScriptMetaData.TYPE, smd);
ClusterState stored = ClusterState.builder(empty).metaData(mdb).build();
scriptService.clusterChanged(new ClusterChangedEvent("test", stored, empty));
return scriptService;
}
public void testCustomGlobalScriptContextSettings() throws Exception {
@ -59,7 +71,7 @@ public class ScriptContextTests extends ESTestCase {
for (ScriptType scriptType : ScriptType.values()) {
try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap());
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op"), Collections.emptyMap());
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op"));
fail("script compilation should have been rejected");
} catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("scripts of type [" + scriptType + "], operation [" + PLUGIN_NAME + "_custom_globally_disabled_op] and lang [" + MockScriptEngine.NAME + "] are disabled"));
@ -71,16 +83,16 @@ public class ScriptContextTests extends ESTestCase {
ScriptService scriptService = makeScriptService();
Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap());
try {
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_exp_disabled_op"), Collections.emptyMap());
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_exp_disabled_op"));
fail("script compilation should have been rejected");
} catch (IllegalStateException e) {
assertTrue(e.getMessage(), e.getMessage().contains("scripts of type [inline], operation [" + PLUGIN_NAME + "_custom_exp_disabled_op] and lang [" + MockScriptEngine.NAME + "] are disabled"));
}
// still works for other script contexts
assertNotNull(scriptService.compile(script, ScriptContext.Standard.AGGS, Collections.emptyMap()));
assertNotNull(scriptService.compile(script, ScriptContext.Standard.SEARCH, Collections.emptyMap()));
assertNotNull(scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_op"), Collections.emptyMap()));
assertNotNull(scriptService.compile(script, ScriptContext.Standard.AGGS));
assertNotNull(scriptService.compile(script, ScriptContext.Standard.SEARCH));
assertNotNull(scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_op")));
}
public void testUnknownPluginScriptContext() throws Exception {
@ -88,7 +100,7 @@ public class ScriptContextTests extends ESTestCase {
for (ScriptType scriptType : ScriptType.values()) {
try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap());
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "unknown"), Collections.emptyMap());
scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "unknown"));
fail("script compilation should have been rejected");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains("script context [" + PLUGIN_NAME + "_unknown] not supported"));
@ -107,7 +119,7 @@ public class ScriptContextTests extends ESTestCase {
for (ScriptType scriptType : ScriptType.values()) {
try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap());
scriptService.compile(script, context, Collections.emptyMap());
scriptService.compile(script, context);
fail("script compilation should have been rejected");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains("script context [test] not supported"));

View File

@ -20,124 +20,83 @@ package org.elasticsearch.script;
import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
import org.elasticsearch.common.io.stream.OutputStreamStreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
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.test.ESTestCase;
import org.elasticsearch.test.AbstractSerializingTestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.io.UncheckedIOException;
public class ScriptMetaDataTests extends ESTestCase {
public class ScriptMetaDataTests extends AbstractSerializingTestCase<ScriptMetaData> {
public void testGetScript() throws Exception {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
XContentBuilder sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().startObject("template").field("field", "value").endObject().endObject();
builder.storeScript("lang", "template", sourceBuilder.bytes());
builder.storeScript("template", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("template", "value").endObject();
builder.storeScript("lang", "template_field", sourceBuilder.bytes());
builder.storeScript("template_field", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().startObject("script").field("field", "value").endObject().endObject();
builder.storeScript("lang", "script", sourceBuilder.bytes());
builder.storeScript("script", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("script", "value").endObject();
builder.storeScript("lang", "script_field", sourceBuilder.bytes());
builder.storeScript("script_field", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("field", "value").endObject();
builder.storeScript("lang", "any", sourceBuilder.bytes());
builder.storeScript("any", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
ScriptMetaData scriptMetaData = builder.build();
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "template"));
assertEquals("value", scriptMetaData.getScript("lang", "template_field"));
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "script"));
assertEquals("value", scriptMetaData.getScript("lang", "script_field"));
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "any"));
}
public void testToAndFromXContent() throws IOException {
XContentType contentType = randomFrom(XContentType.values());
XContentBuilder xContentBuilder = XContentBuilder.builder(contentType.xContent());
ScriptMetaData expected = randomScriptMetaData(contentType);
xContentBuilder.startObject();
expected.toXContent(xContentBuilder, new ToXContent.MapParams(Collections.emptyMap()));
xContentBuilder.endObject();
xContentBuilder = shuffleXContent(xContentBuilder);
XContentParser parser = createParser(xContentBuilder);
parser.nextToken();
ScriptMetaData result = ScriptMetaData.fromXContent(parser);
assertEquals(expected, result);
assertEquals(expected.hashCode(), result.hashCode());
}
public void testReadFromWriteTo() throws IOException {
ScriptMetaData expected = randomScriptMetaData(randomFrom(XContentType.values()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
expected.writeTo(new OutputStreamStreamOutput(out));
ScriptMetaData result = new ScriptMetaData(new InputStreamStreamInput(new ByteArrayInputStream(out.toByteArray())));
assertEquals(expected, result);
assertEquals(expected.hashCode(), result.hashCode());
assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("template", "lang").getCode());
assertEquals("value", scriptMetaData.getStoredScript("template_field", "lang").getCode());
assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("script", "lang").getCode());
assertEquals("value", scriptMetaData.getStoredScript("script_field", "lang").getCode());
assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("any", "lang").getCode());
}
public void testDiff() throws Exception {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("lang", "1", new BytesArray("{\"foo\":\"abc\"}"));
builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"def\"}"));
builder.storeScript("lang", "3", new BytesArray("{\"foo\":\"ghi\"}"));
builder.storeScript("1", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"abc\"}")));
builder.storeScript("2", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"def\"}")));
builder.storeScript("3", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"ghi\"}")));
ScriptMetaData scriptMetaData1 = builder.build();
builder = new ScriptMetaData.Builder(scriptMetaData1);
builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"changed\"}"));
builder.deleteScript("lang", "3");
builder.storeScript("lang", "4", new BytesArray("{\"foo\":\"jkl\"}"));
builder.storeScript("2", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"changed\"}")));
builder.deleteScript("3", "lang");
builder.storeScript("4", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"jkl\"}")));
ScriptMetaData scriptMetaData2 = builder.build();
ScriptMetaData.ScriptMetadataDiff diff = (ScriptMetaData.ScriptMetadataDiff) scriptMetaData2.diff(scriptMetaData1);
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().size());
assertEquals("lang#3", ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().get(0));
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getDiffs().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getDiffs().get("lang#2"));
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getUpserts().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("lang#4"));
assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().size());
assertEquals("3", ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().get(0));
assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getDiffs().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getDiffs().get("2"));
assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getUpserts().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("4"));
ScriptMetaData result = (ScriptMetaData) diff.apply(scriptMetaData1);
assertEquals(new BytesArray("{\"foo\":\"abc\"}"), result.getScriptAsBytes("lang", "1"));
assertEquals(new BytesArray("{\"foo\":\"changed\"}"), result.getScriptAsBytes("lang", "2"));
assertEquals(new BytesArray("{\"foo\":\"jkl\"}"), result.getScriptAsBytes("lang", "4"));
assertEquals("{\"foo\":\"abc\"}", result.getStoredScript("1", "lang").getCode());
assertEquals("{\"foo\":\"changed\"}", result.getStoredScript("2", "lang").getCode());
assertEquals("{\"foo\":\"jkl\"}", result.getStoredScript("4", "lang").getCode());
}
public void testBuilder() {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("_lang", "_id", new BytesArray("{\"script\":\"1 + 1\"}"));
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> builder.storeScript("_lang#", "_id", new BytesArray("{\"foo\": \"bar\"}")));
assertEquals("stored script language can't contain: '#'", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> builder.storeScript("_lang", "_id#", new BytesArray("{\"foo\": \"bar\"}")));
assertEquals("stored script id can't contain: '#'", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> builder.deleteScript("_lang#", "_id"));
assertEquals("stored script language can't contain: '#'", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> builder.deleteScript("_lang", "_id#"));
assertEquals("stored script id can't contain: '#'", e.getMessage());
builder.storeScript("_id", StoredScriptSource.parse("_lang", new BytesArray("{\"script\":\"1 + 1\"}")));
ScriptMetaData result = builder.build();
assertEquals("1 + 1", result.getScript("_lang", "_id"));
assertEquals("1 + 1", result.getStoredScript("_id", "_lang").getCode());
}
private ScriptMetaData randomScriptMetaData(XContentType sourceContentType) throws IOException {
@ -146,10 +105,44 @@ public class ScriptMetaDataTests extends ESTestCase {
for (int i = 0; i < numScripts; i++) {
String lang = randomAsciiOfLength(4);
XContentBuilder sourceBuilder = XContentBuilder.builder(sourceContentType.xContent());
sourceBuilder.startObject().field(randomAsciiOfLength(4), randomAsciiOfLength(4)).endObject();
builder.storeScript(lang, randomAsciiOfLength(i + 1), sourceBuilder.bytes());
sourceBuilder.startObject().field("script", randomAsciiOfLength(4)).endObject();
builder.storeScript(randomAsciiOfLength(i + 1), StoredScriptSource.parse(lang, sourceBuilder.bytes()));
}
return builder.build();
}
@Override
protected ScriptMetaData createTestInstance() {
try {
return randomScriptMetaData(randomFrom(XContentType.values()));
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
@Override
protected Writeable.Reader<ScriptMetaData> instanceReader() {
return ScriptMetaData::new;
}
@Override
protected XContentBuilder toXContent(ScriptMetaData instance, XContentType contentType) throws IOException {
XContentBuilder builder = XContentFactory.contentBuilder(contentType);
if (randomBoolean()) {
builder.prettyPrint();
}
builder.startObject();
instance.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
return builder;
}
@Override
protected ScriptMetaData doParseInstance(XContentParser parser) {
try {
return ScriptMetaData.fromXContent(parser);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
}

View File

@ -19,9 +19,7 @@
package org.elasticsearch.script;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
@ -121,9 +119,9 @@ public class ScriptServiceTests extends ESTestCase {
// TODO:
scriptService = new ScriptService(finalSettings, environment, resourceWatcherService, scriptEngineRegistry, scriptContextRegistry, scriptSettings) {
@Override
String getScriptFromClusterState(String scriptLang, String id) {
StoredScriptSource getScriptFromClusterState(String id, String lang) {
//mock the script that gets retrieved from an index
return "100";
return new StoredScriptSource(lang, "100", Collections.emptyMap());
}
};
}
@ -170,7 +168,7 @@ public class ScriptServiceTests extends ESTestCase {
resourceWatcherService.notifyNow();
CompiledScript compiledScript = scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()),
ScriptContext.Standard.SEARCH, Collections.emptyMap());
ScriptContext.Standard.SEARCH);
assertThat(compiledScript.compiled(), equalTo((Object) "compiled_test_file"));
Files.delete(testFileNoExt);
@ -178,11 +176,10 @@ public class ScriptServiceTests extends ESTestCase {
resourceWatcherService.notifyNow();
try {
scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()), ScriptContext.Standard.SEARCH,
Collections.emptyMap());
scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()), ScriptContext.Standard.SEARCH);
fail("the script test_script should no longer exist");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), containsString("Unable to find on disk file script [test_script] using lang [test]"));
assertThat(ex.getMessage(), containsString("unable to find file script [test_script] using lang [test]"));
}
}
@ -197,7 +194,7 @@ public class ScriptServiceTests extends ESTestCase {
resourceWatcherService.notifyNow();
CompiledScript compiledScript = scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()),
ScriptContext.Standard.SEARCH, Collections.emptyMap());
ScriptContext.Standard.SEARCH);
assertThat(compiledScript.compiled(), equalTo((Object) "compiled_test_file_script"));
Files.delete(testHiddenFile);
@ -208,9 +205,9 @@ public class ScriptServiceTests extends ESTestCase {
public void testInlineScriptCompiledOnceCache() throws IOException {
buildScriptService(Settings.EMPTY);
CompiledScript compiledScript1 = scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()),
randomFrom(scriptContexts), Collections.emptyMap());
randomFrom(scriptContexts));
CompiledScript compiledScript2 = scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()),
randomFrom(scriptContexts), Collections.emptyMap());
randomFrom(scriptContexts));
assertThat(compiledScript1.compiled(), sameInstance(compiledScript2.compiled()));
}
@ -333,7 +330,7 @@ public class ScriptServiceTests extends ESTestCase {
String type = scriptEngineService.getType();
try {
scriptService.compile(new Script(randomFrom(ScriptType.values()), type, "test", Collections.emptyMap()),
new ScriptContext.Plugin(pluginName, unknownContext), Collections.emptyMap());
new ScriptContext.Plugin(pluginName, unknownContext));
fail("script compilation should have been rejected");
} catch(IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("script context [" + pluginName + "_" + unknownContext + "] not supported"));
@ -342,8 +339,7 @@ public class ScriptServiceTests extends ESTestCase {
public void testCompileCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), randomFrom(scriptContexts),
Collections.emptyMap());
scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@ -364,8 +360,7 @@ public class ScriptServiceTests extends ESTestCase {
int numberOfCompilations = randomIntBetween(1, 1024);
for (int i = 0; i < numberOfCompilations; i++) {
scriptService
.compile(new Script(ScriptType.INLINE, "test", i + " + " + i, Collections.emptyMap()), randomFrom(scriptContexts),
Collections.emptyMap());
.compile(new Script(ScriptType.INLINE, "test", i + " + " + i, Collections.emptyMap()), randomFrom(scriptContexts));
}
assertEquals(numberOfCompilations, scriptService.stats().getCompilations());
}
@ -383,15 +378,13 @@ public class ScriptServiceTests extends ESTestCase {
public void testFileScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
createFileScripts("test");
scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()), randomFrom(scriptContexts),
Collections.emptyMap());
scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
public void testIndexedScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.compile(new Script(ScriptType.STORED, "test", "script", Collections.emptyMap()), randomFrom(scriptContexts),
Collections.emptyMap());
scriptService.compile(new Script(ScriptType.STORED, "test", "script", Collections.emptyMap()), randomFrom(scriptContexts));
assertEquals(1L, scriptService.stats().getCompilations());
}
@ -411,8 +404,7 @@ public class ScriptServiceTests extends ESTestCase {
builder.put("script.inline", "true");
buildScriptService(builder.build());
CompiledScript script = scriptService.compile(
new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, "1 + 1", Collections.emptyMap()),
randomFrom(scriptContexts), Collections.emptyMap());
new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, "1 + 1", Collections.emptyMap()), randomFrom(scriptContexts));
assertEquals(script.lang(), Script.DEFAULT_SCRIPT_LANG);
}
@ -421,34 +413,23 @@ public class ScriptServiceTests extends ESTestCase {
.field("script", "abc")
.endObject().bytes();
ClusterState empty = ClusterState.builder(new ClusterName("_name")).build();
PutStoredScriptRequest request = new PutStoredScriptRequest("_lang", "_id")
.script(script);
ClusterState result = ScriptService.innerStoreScript(empty, "_lang", request);
ScriptMetaData scriptMetaData = result.getMetaData().custom(ScriptMetaData.TYPE);
ScriptMetaData scriptMetaData = ScriptMetaData.putStoredScript(null, "_id", StoredScriptSource.parse("_lang", script));
assertNotNull(scriptMetaData);
assertEquals("abc", scriptMetaData.getScript("_lang", "_id"));
assertEquals("abc", scriptMetaData.getStoredScript("_id", "_lang").getCode());
}
public void testDeleteScript() throws Exception {
ClusterState cs = ClusterState.builder(new ClusterName("_name"))
.metaData(MetaData.builder()
.putCustom(ScriptMetaData.TYPE,
new ScriptMetaData.Builder(null).storeScript("_lang", "_id",
new BytesArray("{\"script\":\"abc\"}")).build()))
.build();
DeleteStoredScriptRequest request = new DeleteStoredScriptRequest("_lang", "_id");
ClusterState result = ScriptService.innerDeleteScript(cs, "_lang", request);
ScriptMetaData scriptMetaData = result.getMetaData().custom(ScriptMetaData.TYPE);
ScriptMetaData scriptMetaData = ScriptMetaData.putStoredScript(null, "_id",
StoredScriptSource.parse("_lang", new BytesArray("{\"script\":\"abc\"}")));
scriptMetaData = ScriptMetaData.deleteStoredScript(scriptMetaData, "_id", "_lang");
assertNotNull(scriptMetaData);
assertNull(scriptMetaData.getScript("_lang", "_id"));
assertNull(scriptMetaData.getScriptAsBytes("_lang", "_id"));
assertNull(scriptMetaData.getStoredScript("_id", "_lang"));
ScriptMetaData errorMetaData = scriptMetaData;
ResourceNotFoundException e = expectThrows(ResourceNotFoundException.class, () -> {
ScriptService.innerDeleteScript(cs, "_lang", new DeleteStoredScriptRequest("_lang", "_non_existing_id"));
ScriptMetaData.deleteStoredScript(errorMetaData, "_id", "_lang");
});
assertEquals("Stored script with id [_non_existing_id] for language [_lang] does not exist", e.getMessage());
assertEquals("stored script [_id] using lang [_lang] does not exist and cannot be deleted", e.getMessage());
}
public void testGetStoredScript() throws Exception {
@ -456,33 +437,17 @@ public class ScriptServiceTests extends ESTestCase {
ClusterState cs = ClusterState.builder(new ClusterName("_name"))
.metaData(MetaData.builder()
.putCustom(ScriptMetaData.TYPE,
new ScriptMetaData.Builder(null).storeScript("_lang", "_id",
new BytesArray("{\"script\":\"abc\"}")).build()))
new ScriptMetaData.Builder(null).storeScript("_id",
StoredScriptSource.parse("_lang", new BytesArray("{\"script\":\"abc\"}"))).build()))
.build();
assertEquals("abc", scriptService.getStoredScript(cs, new GetStoredScriptRequest("_lang", "_id")));
assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_lang", "_id2")));
assertEquals("abc", scriptService.getStoredScript(cs, new GetStoredScriptRequest("_id", "_lang")).getCode());
assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_id2", "_lang")));
cs = ClusterState.builder(new ClusterName("_name")).build();
assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_lang", "_id")));
assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_id", "_lang")));
}
public void testValidateScriptSize() throws Exception {
int maxSize = 0xFFFF;
buildScriptService(Settings.EMPTY);
// allowed
scriptService.validateStoredScript("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 13) + "\"}"));
// disallowed
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> {
scriptService.validateStoredScript("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 12) + "\"}"));
});
assertThat(e.getMessage(), equalTo(
"Limit of script size in bytes [" + maxSize+ "] has been exceeded for script [_id] with size [" + (maxSize + 1) + "]"));
}
private void createFileScripts(String... langs) throws IOException {
for (String lang : langs) {
Path scriptPath = scriptsFilePath.resolve("file_script." + lang);
@ -493,7 +458,7 @@ public class ScriptServiceTests extends ESTestCase {
private void assertCompileRejected(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) {
try {
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext, Collections.emptyMap());
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext);
fail("compile should have been rejected for lang [" + lang + "], script_type [" + scriptType + "], scripted_op [" + scriptContext + "]");
} catch(IllegalStateException e) {
//all good
@ -502,7 +467,7 @@ public class ScriptServiceTests extends ESTestCase {
private void assertCompileAccepted(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) {
assertThat(
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext, Collections.emptyMap()),
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext),
notNullValue()
);
}

View File

@ -78,10 +78,9 @@ public class ScriptTests extends ESTestCase {
}
return new Script(
scriptType,
randomFrom("_lang1", "_lang2", "_lang3"),
scriptType == ScriptType.STORED ? null : randomFrom("_lang1", "_lang2", "_lang3"),
script,
scriptType == ScriptType.INLINE ?
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, xContent.type().mediaType()) : Collections.emptyMap(),
scriptType == ScriptType.INLINE ? Collections.singletonMap(Script.CONTENT_TYPE_OPTION, xContent.type().mediaType()) : null,
params
);
}

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.ResourceNotFoundException;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.Writeable;
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.test.AbstractSerializingTestCase;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
public class StoredScriptTests extends AbstractSerializingTestCase<StoredScriptSource> {
public void testBasicAddDelete() {
StoredScriptSource source = new StoredScriptSource("lang", "code", emptyMap());
ScriptMetaData smd = ScriptMetaData.putStoredScript(null, "test", source);
assertThat(smd.getStoredScript("test", null), equalTo(source));
assertThat(smd.getStoredScript("test", "lang"), equalTo(source));
smd = ScriptMetaData.deleteStoredScript(smd, "test", null);
assertThat(smd.getStoredScript("test", null), nullValue());
assertThat(smd.getStoredScript("test", "lang"), nullValue());
}
public void testDifferentMultiAddDelete() {
StoredScriptSource source0 = new StoredScriptSource("lang0", "code0", emptyMap());
StoredScriptSource source1 = new StoredScriptSource("lang0", "code1", emptyMap());
StoredScriptSource source2 = new StoredScriptSource("lang1", "code2",
singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType()));
ScriptMetaData smd = ScriptMetaData.putStoredScript(null, "test0", source0);
smd = ScriptMetaData.putStoredScript(smd, "test1", source1);
smd = ScriptMetaData.putStoredScript(smd, "test2", source2);
assertThat(smd.getStoredScript("test0", null), equalTo(source0));
assertThat(smd.getStoredScript("test0", "lang0"), equalTo(source0));
assertThat(smd.getStoredScript("test1", null), equalTo(source1));
assertThat(smd.getStoredScript("test1", "lang0"), equalTo(source1));
assertThat(smd.getStoredScript("test2", null), equalTo(source2));
assertThat(smd.getStoredScript("test2", "lang1"), equalTo(source2));
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test2", "lang0"), nullValue());
smd = ScriptMetaData.deleteStoredScript(smd, "test0", null);
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), nullValue());
assertThat(smd.getStoredScript("test1", null), equalTo(source1));
assertThat(smd.getStoredScript("test1", "lang0"), equalTo(source1));
assertThat(smd.getStoredScript("test2", null), equalTo(source2));
assertThat(smd.getStoredScript("test2", "lang1"), equalTo(source2));
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test2", "lang0"), nullValue());
smd = ScriptMetaData.deleteStoredScript(smd, "test2", "lang1");
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), nullValue());
assertThat(smd.getStoredScript("test1", null), equalTo(source1));
assertThat(smd.getStoredScript("test1", "lang0"), equalTo(source1));
assertThat(smd.getStoredScript("test2", null), nullValue());
assertThat(smd.getStoredScript("test2", "lang1"), nullValue());
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test2", "lang0"), nullValue());
smd = ScriptMetaData.deleteStoredScript(smd, "test1", "lang0");
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), nullValue());
assertThat(smd.getStoredScript("test1", null), nullValue());
assertThat(smd.getStoredScript("test1", "lang0"), nullValue());
assertThat(smd.getStoredScript("test2", null), nullValue());
assertThat(smd.getStoredScript("test2", "lang1"), nullValue());
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test2", "lang0"), nullValue());
}
public void testSameMultiAddDelete() {
StoredScriptSource source0 = new StoredScriptSource("lang0", "code0", emptyMap());
StoredScriptSource source1 = new StoredScriptSource("lang1", "code1", emptyMap());
StoredScriptSource source2 = new StoredScriptSource("lang2", "code1", emptyMap());
StoredScriptSource source3 = new StoredScriptSource("lang1", "code2",
singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType()));
ScriptMetaData smd = ScriptMetaData.putStoredScript(null, "test0", source0);
smd = ScriptMetaData.putStoredScript(smd, "test0", source1);
assertWarnings("stored script [test0] already exists using a different lang [lang0]," +
" the new namespace for stored scripts will only use (id) instead of (lang, id)");
smd = ScriptMetaData.putStoredScript(smd, "test3", source3);
smd = ScriptMetaData.putStoredScript(smd, "test0", source2);
assertWarnings("stored script [test0] already exists using a different lang [lang1]," +
" the new namespace for stored scripts will only use (id) instead of (lang, id)");
assertThat(smd.getStoredScript("test0", null), equalTo(source2));
assertThat(smd.getStoredScript("test0", "lang0"), equalTo(source0));
assertThat(smd.getStoredScript("test0", "lang1"), equalTo(source1));
assertThat(smd.getStoredScript("test0", "lang2"), equalTo(source2));
assertThat(smd.getStoredScript("test3", null), equalTo(source3));
assertThat(smd.getStoredScript("test3", "lang1"), equalTo(source3));
smd = ScriptMetaData.deleteStoredScript(smd, "test0", "lang1");
assertThat(smd.getStoredScript("test0", null), equalTo(source2));
assertThat(smd.getStoredScript("test0", "lang0"), equalTo(source0));
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test0", "lang2"), equalTo(source2));
assertThat(smd.getStoredScript("test3", null), equalTo(source3));
assertThat(smd.getStoredScript("test3", "lang1"), equalTo(source3));
smd = ScriptMetaData.deleteStoredScript(smd, "test0", null);
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), equalTo(source0));
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test0", "lang2"), nullValue());
assertThat(smd.getStoredScript("test3", null), equalTo(source3));
assertThat(smd.getStoredScript("test3", "lang1"), equalTo(source3));
smd = ScriptMetaData.deleteStoredScript(smd, "test3", "lang1");
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), equalTo(source0));
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test0", "lang2"), nullValue());
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test3", "lang1"), nullValue());
smd = ScriptMetaData.deleteStoredScript(smd, "test0", "lang0");
assertThat(smd.getStoredScript("test0", null), nullValue());
assertThat(smd.getStoredScript("test0", "lang0"), nullValue());
assertThat(smd.getStoredScript("test0", "lang1"), nullValue());
assertThat(smd.getStoredScript("test0", "lang2"), nullValue());
assertThat(smd.getStoredScript("test3", null), nullValue());
assertThat(smd.getStoredScript("test3", "lang1"), nullValue());
}
public void testInvalidDelete() {
ResourceNotFoundException rnfe =
expectThrows(ResourceNotFoundException.class, () -> ScriptMetaData.deleteStoredScript(null, "test", "lang"));
assertThat(rnfe.getMessage(), equalTo("stored script [test] using lang [lang] does not exist and cannot be deleted"));
rnfe = expectThrows(ResourceNotFoundException.class, () -> ScriptMetaData.deleteStoredScript(null, "test", null));
assertThat(rnfe.getMessage(), equalTo("stored script [test] does not exist and cannot be deleted"));
}
public void testSourceParsing() throws Exception {
// simple script value string
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script", "code").endObject();
StoredScriptSource parsed = StoredScriptSource.parse("lang", builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", "code", Collections.emptyMap());
assertThat(parsed, equalTo(source));
}
// simple template value string
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("template", "code").endObject();
StoredScriptSource parsed = StoredScriptSource.parse("lang", builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", "code",
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, builder.contentType().mediaType()));
assertThat(parsed, equalTo(source));
}
// complex template with wrapper template object
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("template").startObject().field("query", "code").endObject().endObject();
String code;
try (XContentBuilder cb = XContentFactory.contentBuilder(builder.contentType())) {
code = cb.startObject().field("query", "code").endObject().string();
}
StoredScriptSource parsed = StoredScriptSource.parse("lang", builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", code,
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, builder.contentType().mediaType()));
assertThat(parsed, equalTo(source));
}
// complex template with no wrapper object
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("query", "code").endObject();
String code;
try (XContentBuilder cb = XContentFactory.contentBuilder(builder.contentType())) {
code = cb.startObject().field("query", "code").endObject().string();
}
StoredScriptSource parsed = StoredScriptSource.parse("lang", builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", code,
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, builder.contentType().mediaType()));
assertThat(parsed, equalTo(source));
}
// complex template using script as the field name
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("query", "code").endObject().endObject();
String code;
try (XContentBuilder cb = XContentFactory.contentBuilder(builder.contentType())) {
code = cb.startObject().field("query", "code").endObject().string();
}
StoredScriptSource parsed = StoredScriptSource.parse("lang", builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", code,
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, builder.contentType().mediaType()));
assertThat(parsed, equalTo(source));
}
// complex script with script object
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").field("code", "code").endObject().endObject();
StoredScriptSource parsed = StoredScriptSource.parse(null, builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", "code", Collections.emptyMap());
assertThat(parsed, equalTo(source));
}
// complex script with script object and empty options
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").field("code", "code")
.field("options").startObject().endObject().endObject().endObject();
StoredScriptSource parsed = StoredScriptSource.parse(null, builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", "code", Collections.emptyMap());
assertThat(parsed, equalTo(source));
}
// complex script with embedded template
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").startObject("code").field("query", "code")
.endObject().startObject("options").endObject().endObject().endObject().string();
String code;
try (XContentBuilder cb = XContentFactory.contentBuilder(builder.contentType())) {
code = cb.startObject().field("query", "code").endObject().string();
}
StoredScriptSource parsed = StoredScriptSource.parse(null, builder.bytes());
StoredScriptSource source = new StoredScriptSource("lang", code,
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, builder.contentType().mediaType()));
assertThat(parsed, equalTo(source));
}
}
public void testSourceParsingErrors() throws Exception {
// check for missing lang parameter when parsing a template
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("template", "code").endObject();
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
StoredScriptSource.parse(null, builder.bytes()));
assertThat(iae.getMessage(), equalTo("unexpected stored script format"));
}
// check for missing lang parameter when parsing a script
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("code", "code").endObject().endObject();
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
StoredScriptSource.parse(null, builder.bytes()));
assertThat(iae.getMessage(), equalTo("must specify lang for stored script"));
}
// check for missing code parameter when parsing a script
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").endObject().endObject();
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
StoredScriptSource.parse(null, builder.bytes()));
assertThat(iae.getMessage(), equalTo("must specify code for stored script"));
}
// check for illegal options parameter when parsing a script
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").field("code", "code")
.startObject("options").field("option", "option").endObject().endObject().endObject();
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
StoredScriptSource.parse(null, builder.bytes()));
assertThat(iae.getMessage(), equalTo("illegal compiler options [{option=option}] specified"));
}
// check for illegal use of content type option
try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
builder.startObject().field("script").startObject().field("lang", "lang").field("code", "code")
.startObject("options").field("content_type", "option").endObject().endObject().endObject();
ParsingException pe = expectThrows(ParsingException.class, () ->
StoredScriptSource.parse(null, builder.bytes()));
assertThat(pe.getRootCause().getMessage(), equalTo("content_type cannot be user-specified"));
}
}
@Override
protected StoredScriptSource createTestInstance() {
return new StoredScriptSource(
randomAsciiOfLength(randomIntBetween(4, 32)),
randomAsciiOfLength(randomIntBetween(4, 16383)),
Collections.emptyMap());
}
@Override
protected Writeable.Reader<StoredScriptSource> instanceReader() {
return StoredScriptSource::new;
}
@Override
protected StoredScriptSource doParseInstance(XContentParser parser) {
try {
return StoredScriptSource.fromXContent(parser);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
}

View File

@ -50,37 +50,38 @@ public class StoredScriptsIT extends ESIntegTestCase {
public void testBasics() {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(LANG)
.setLang(LANG)
.setId("foobar")
.setSource(new BytesArray("{\"script\":\"1\"}")));
.setContent(new BytesArray("{\"script\":\"1\"}")));
String script = client().admin().cluster().prepareGetStoredScript(LANG, "foobar")
.get().getStoredScript();
.get().getSource().getCode();
assertNotNull(script);
assertEquals("1", script);
assertAcked(client().admin().cluster().prepareDeleteStoredScript()
.setId("foobar")
.setScriptLang(LANG));
script = client().admin().cluster().prepareGetStoredScript(LANG, "foobar")
.get().getStoredScript();
assertNull(script);
.setLang(LANG));
StoredScriptSource source = client().admin().cluster().prepareGetStoredScript(LANG, "foobar")
.get().getSource();
assertNull(source);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript()
.setScriptLang("lang#")
.setLang("lang#")
.setId("id#")
.setSource(new BytesArray("{}"))
.setContent(new BytesArray("{}"))
.get());
assertEquals("Validation Failed: 1: id can't contain: '#';2: lang can't contain: '#';", e.getMessage());
assertEquals("Validation Failed: 1: id cannot contain '#' for stored script;" +
"2: lang cannot contain '#' for stored script;", e.getMessage());
}
public void testMaxScriptSize() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript()
.setScriptLang(LANG)
.setLang(LANG)
.setId("foobar")
.setSource(new BytesArray(randomAsciiOfLength(SCRIPT_MAX_SIZE_IN_BYTES + 1)))
.setContent(new BytesArray(randomAsciiOfLength(SCRIPT_MAX_SIZE_IN_BYTES + 1)))
.get()
);
assertEquals("Limit of script size in bytes [64] has been exceeded for script [foobar] with size [65]", e.getMessage());
assertEquals("exceeded max allowed stored script size in bytes [64] with size [65] for script [foobar]", e.getMessage());
}
public static class CustomScriptPlugin extends MockScriptPlugin {

View File

@ -228,24 +228,24 @@ public class ScriptedMetricIT extends ESIntegTestCase {
// the id of the stored script is used in test method while the source of the stored script
// must match a predefined script from CustomScriptPlugin.pluginScripts() method
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
.setId("initScript_stored")
.setSource(new BytesArray("{\"script\":\"vars.multiplier = 3\"}")));
.setContent(new BytesArray("{\"script\":\"vars.multiplier = 3\"}")));
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
.setId("mapScript_stored")
.setSource(new BytesArray("{\"script\":\"_agg.add(vars.multiplier)\"}")));
.setContent(new BytesArray("{\"script\":\"_agg.add(vars.multiplier)\"}")));
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
.setId("combineScript_stored")
.setSource(new BytesArray("{\"script\":\"sum agg values as a new aggregation\"}")));
.setContent(new BytesArray("{\"script\":\"sum agg values as a new aggregation\"}")));
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
.setId("reduceScript_stored")
.setSource(new BytesArray("{\"script\":\"sum aggs of agg values as a new aggregation\"}")));
.setContent(new BytesArray("{\"script\":\"sum aggs of agg values as a new aggregation\"}")));
indexRandom(true, builders);
ensureSearchable();

View File

@ -55,8 +55,9 @@ public class ScriptedMetricTests extends BaseAggregationTestCase<ScriptedMetricA
if (randomBoolean()) {
return new Script(script);
} else {
ScriptType type = randomFrom(ScriptType.values());
return new Script(
randomFrom(ScriptType.values()), randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG), script, Collections.emptyMap());
type, type == ScriptType.STORED ? null : randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG), script, Collections.emptyMap());
}
}

View File

@ -480,9 +480,9 @@ public class BucketScriptIT extends ESIntegTestCase {
public void testStoredScript() {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setId("my_script")
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
// Script source is not interpreted but it references a pre-defined script from CustomScriptPlugin
.setSource(new BytesArray("{ \"script\": \"my_script\" }")));
.setContent(new BytesArray("{ \"script\": \"my_script\" }")));
SearchResponse response = client()
.prepareSearch("idx")

View File

@ -46,7 +46,9 @@ public class BucketScriptTests extends BasePipelineAggregationTestCase<BucketScr
if (randomBoolean()) {
params.put("foo", "bar");
}
script = new Script(randomFrom(ScriptType.values()), randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG), "script", params);
ScriptType type = randomFrom(ScriptType.values());
script = new Script(type, type == ScriptType.STORED ? null : randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG),
"script", params);
}
BucketScriptPipelineAggregationBuilder factory = new BucketScriptPipelineAggregationBuilder(name, bucketsPaths, script);
if (randomBoolean()) {

View File

@ -432,9 +432,9 @@ public class BucketSelectorIT extends ESIntegTestCase {
public void testStoredScript() {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setId("my_script")
.setScriptLang(CustomScriptPlugin.NAME)
.setLang(CustomScriptPlugin.NAME)
// Source is not interpreted but my_script is defined in CustomScriptPlugin
.setSource(new BytesArray("{ \"script\": \"Double.isNaN(_value0) ? false : (_value0 + _value1 > 100)\" }")));
.setContent(new BytesArray("{ \"script\": \"Double.isNaN(_value0) ? false : (_value0 + _value1 > 100)\" }")));
Script script = new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "my_script", Collections.emptyMap());

View File

@ -46,7 +46,9 @@ public class BucketSelectorTests extends BasePipelineAggregationTestCase<BucketS
if (randomBoolean()) {
params.put("foo", "bar");
}
script = new Script(randomFrom(ScriptType.values()), randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG), "script", params);
ScriptType type = randomFrom(ScriptType.values());
script =
new Script(type, type == ScriptType.STORED ? null : randomFrom("my_lang", Script.DEFAULT_SCRIPT_LANG), "script", params);
}
BucketSelectorPipelineAggregationBuilder factory = new BucketSelectorPipelineAggregationBuilder(name, bucketsPaths, script);
if (randomBoolean()) {

View File

@ -97,7 +97,7 @@ public abstract class AbstractSortTestCase<T extends SortBuilder<T>> extends EST
scriptService = new ScriptService(baseSettings, environment,
new ResourceWatcherService(baseSettings, null), scriptEngineRegistry, scriptContextRegistry, scriptSettings) {
@Override
public CompiledScript compile(Script script, ScriptContext scriptContext, Map<String, String> params) {
public CompiledScript compile(Script script, ScriptContext scriptContext) {
return new CompiledScript(ScriptType.INLINE, "mockName", "test", script);
}
};

View File

@ -539,9 +539,9 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
if(testScript) {
logger.info("--> creating test script");
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MockScriptEngine.NAME)
.setLang(MockScriptEngine.NAME)
.setId("foobar")
.setSource(new BytesArray("{\"script\":\"1\"}")));
.setContent(new BytesArray("{\"script\":\"1\"}")));
}
logger.info("--> snapshot without global state");
@ -600,7 +600,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
if (testScript) {
logger.info("--> check that script is restored");
GetStoredScriptResponse getStoredScriptResponse = client().admin().cluster().prepareGetStoredScript(MockScriptEngine.NAME, "foobar").get();
assertNotNull(getStoredScriptResponse.getStoredScript());
assertNotNull(getStoredScriptResponse.getSource());
}
createIndex("test-idx");
@ -644,7 +644,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
getIndexTemplatesResponse = client().admin().indices().prepareGetTemplates().get();
assertIndexTemplateMissing(getIndexTemplatesResponse, "test-template");
assertFalse(client().admin().cluster().prepareGetPipeline("barbaz").get().isFound());
assertNull(client().admin().cluster().prepareGetStoredScript(MockScriptEngine.NAME, "foobar").get().getStoredScript());
assertNull(client().admin().cluster().prepareGetStoredScript(MockScriptEngine.NAME, "foobar").get().getSource());
assertThat(client.prepareSearch("test-idx").setSize(0).get().getHits().totalHits(), equalTo(100L));
}

View File

@ -54,11 +54,11 @@ GET my_index/_search
setting `script.default_lang` to the appropriate language.
`inline`, `id`, `file`::
`inline`, `stored`, `file`::
Specifies the source of the script. An `inline` script is specified
`inline` as in the example above, a stored script with the specified `id`
is retrieved from the cluster state (see <<modules-scripting-stored-scripts,Stored Scripts>>),
`inline` as in the example above, a `stored` script is specified `stored`
and is retrieved from the cluster state (see <<modules-scripting-stored-scripts,Stored Scripts>>),
and a `file` script is retrieved from a file in the `config/scripts`
directory (see <<modules-scripting-file-scripts, File Scripts>>).
+
@ -164,7 +164,6 @@ hierarchy of directories is flattened and concatenated with underscores. A
script in `group1/group2/my_script.painless` should use `group1_group2_myscript`
as the `file` name.
[[reload-scripts]]
[float]
==== Automatic script reloading
@ -181,23 +180,51 @@ Script reloading can be completely disabled by setting
=== Stored Scripts
Scripts may be stored in and retrieved from the cluster state using the
`_scripts` end-point:
`_scripts` end-point.
==== Deprecated Namespace
The namespace for stored scripts using both `lang` and `id` as a unique
identifier has been deprecated. The new namespace for stored scripts will
only use `id`. Stored scripts with the same `id`, but different `lang`'s
will no longer be allowed in 6.0. To comply with the new namespace for
stored scripts, existing stored scripts should be deleted and put again.
Any scripts that share an `id` but have different `lang`s will need to
be re-named. For example, take the following:
"id": "example", "lang": "painless"
"id": "example", "lang": "expressions"
The above scripts will conflict under the new namespace since the id's are
the same. At least one will have to be re-named to comply with the new
namespace of only `id`.
As a final caveat, stored search templates and stored scripts share
the same namespace, so if a search template has the same `id` as a
stored script, one of the two will have to be re-named as well using
delete and put requests.
==== Request Examples
The following are examples of stored script requests:
[source,js]
-----------------------------------
/_scripts/{lang}/{id} <1> <2>
/_scripts/{id} <1>
-----------------------------------
<1> The `lang` represents the script language.
<2> The `id` is a unique identifier or script name.
<1> The `id` is a unique identifier for the stored script.
This example stores a Painless script called `calculate-score` in the cluster
state:
[source,js]
-----------------------------------
POST _scripts/painless/calculate-score
POST _scripts/calculate-score
{
"script": "Math.log(_score * 2) + params.my_modifier"
"script": {
"lang": "painless",
"code": "Math.log(_score * 2) + params.my_modifier"
}
}
-----------------------------------
// CONSOLE
@ -206,12 +233,12 @@ This same script can be retrieved with:
[source,js]
-----------------------------------
GET _scripts/painless/calculate-score
GET _scripts/calculate-score
-----------------------------------
// CONSOLE
// TEST[continued]
Stored scripts can be used by specifying the `lang` and `stored` parameters as follows:
Stored scripts can be used by specifying the `stored` parameters as follows:
[source,js]
--------------------------------------------------
@ -220,7 +247,6 @@ GET _search
"query": {
"script": {
"script": {
"lang": "painless",
"stored": "calculate-score",
"params": {
"my_modifier": 2
@ -237,7 +263,7 @@ And deleted with:
[source,js]
-----------------------------------
DELETE _scripts/painless/calculate-score
DELETE _scripts/calculate-score
-----------------------------------
// CONSOLE
// TEST[continued]

View File

@ -19,8 +19,6 @@
package org.elasticsearch.ingest.common;
import java.util.Map;
import org.elasticsearch.common.Strings;
import org.elasticsearch.ingest.AbstractProcessor;
import org.elasticsearch.ingest.IngestDocument;
@ -30,7 +28,8 @@ import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptType;
import java.util.Map;
import static java.util.Collections.emptyMap;
import static org.elasticsearch.common.Strings.hasLength;
@ -139,7 +138,7 @@ public final class ScriptProcessor extends AbstractProcessor {
// verify script is able to be compiled before successfully creating processor.
try {
scriptService.compile(script, ScriptContext.Standard.INGEST, script.getOptions());
scriptService.compile(script, ScriptContext.Standard.INGEST);
} catch (ScriptException e) {
throw newConfigurationException(TYPE, processorTag, scriptPropertyUsed, e);
}

View File

@ -107,7 +107,7 @@ public class ScriptProcessorFactoryTests extends ESTestCase {
ScriptService mockedScriptService = mock(ScriptService.class);
ScriptException thrownException = new ScriptException("compile-time exception", new RuntimeException(),
Collections.emptyList(), "script", "mockscript");
when(mockedScriptService.compile(any(), any(), any())).thenThrow(thrownException);
when(mockedScriptService.compile(any(), any())).thenThrow(thrownException);
factory = new ScriptProcessor.Factory(mockedScriptService);
Map<String, Object> configMap = new HashMap<>();

View File

@ -35,7 +35,7 @@ import java.util.Collections;
import static org.hamcrest.Matchers.containsString;
//TODO: please convert to unit tests!
public class IndexedExpressionTests extends ESIntegTestCase {
public class StoredExpressionTests extends ESIntegTestCase {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal));
@ -51,9 +51,9 @@ public class IndexedExpressionTests extends ESIntegTestCase {
public void testAllOpsDisabledIndexedScripts() throws IOException {
client().admin().cluster().preparePutStoredScript()
.setScriptLang(ExpressionScriptEngineService.NAME)
.setLang(ExpressionScriptEngineService.NAME)
.setId("script1")
.setSource(new BytesArray("{\"script\":\"2\"}"))
.setContent(new BytesArray("{\"script\":\"2\"}"))
.get();
client().prepareIndex("test", "scriptTest", "1").setSource("{\"theField\":\"foo\"}").get();
try {
@ -62,7 +62,7 @@ public class IndexedExpressionTests extends ESIntegTestCase {
fail("update script should have been rejected");
} catch(Exception e) {
assertThat(e.getMessage(), containsString("failed to execute script"));
assertThat(e.getCause().getMessage(), containsString("scripts of type [stored], operation [update] and lang [expression] are disabled"));
assertThat(e.getCause().getMessage(), containsString("scripts of type [stored], operation [update] and lang [expression] are not supported"));
}
try {
client().prepareSearch()

View File

@ -18,21 +18,32 @@
*/
package org.elasticsearch.script.mustache;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.admin.cluster.RestDeleteStoredScriptAction;
import org.elasticsearch.rest.action.AcknowledgedRestListener;
import org.elasticsearch.script.Script;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteSearchTemplateAction extends RestDeleteStoredScriptAction {
public class RestDeleteSearchTemplateAction extends BaseRestHandler {
public RestDeleteSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false);
super(settings);
controller.registerHandler(DELETE, "/_search/template/{id}", this);
}
@Override
protected String getScriptLang(RestRequest request) {
return "mustache";
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
String id = request.param("id");
DeleteStoredScriptRequest deleteStoredScriptRequest = new DeleteStoredScriptRequest(id, Script.DEFAULT_TEMPLATE_LANG);
return channel -> client.admin().cluster().deleteStoredScript(deleteStoredScriptRequest, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -18,29 +18,64 @@
*/
package org.elasticsearch.script.mustache;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.StoredScriptSource;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.GET;
public class RestGetSearchTemplateAction extends RestGetStoredScriptAction {
public class RestGetSearchTemplateAction extends BaseRestHandler {
private static final String TEMPLATE = "template";
public static final ParseField _ID_PARSE_FIELD = new ParseField("_id");
public static final ParseField FOUND_PARSE_FIELD = new ParseField("found");
public RestGetSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false);
super(settings);
controller.registerHandler(GET, "/_search/template/{id}", this);
}
@Override
protected String getScriptLang(RestRequest request) {
return "mustache";
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException {
String id = request.param("id");
GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id, Script.DEFAULT_TEMPLATE_LANG);
return channel -> client.admin().cluster().getStoredScript(getRequest, new RestBuilderListener<GetStoredScriptResponse>(channel) {
@Override
public RestResponse buildResponse(GetStoredScriptResponse response, XContentBuilder builder) throws Exception {
builder.startObject();
builder.field(_ID_PARSE_FIELD.getPreferredName(), id);
builder.field(StoredScriptSource.LANG_PARSE_FIELD.getPreferredName(), Script.DEFAULT_TEMPLATE_LANG);
StoredScriptSource source = response.getSource();
boolean found = source != null;
builder.field(FOUND_PARSE_FIELD.getPreferredName(), found);
if (found) {
builder.field(StoredScriptSource.TEMPLATE_PARSE_FIELD.getPreferredName(), source.getCode());
}
@Override
protected String getScriptFieldName() {
return TEMPLATE;
builder.endObject();
return new BytesRestResponse(found ? RestStatus.OK : RestStatus.NOT_FOUND, builder);
}
});
}
}

View File

@ -18,23 +18,36 @@
*/
package org.elasticsearch.script.mustache;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.admin.cluster.RestPutStoredScriptAction;
import org.elasticsearch.rest.action.AcknowledgedRestListener;
import org.elasticsearch.script.Script;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutSearchTemplateAction extends RestPutStoredScriptAction {
public class RestPutSearchTemplateAction extends BaseRestHandler {
public RestPutSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false);
super(settings);
controller.registerHandler(POST, "/_search/template/{id}", this);
controller.registerHandler(PUT, "/_search/template/{id}", this);
}
@Override
protected String getScriptLang(RestRequest request) {
return "mustache";
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
String id = request.param("id");
BytesReference content = request.content();
PutStoredScriptRequest put = new PutStoredScriptRequest(id, Script.DEFAULT_TEMPLATE_LANG, content);
return channel -> client.admin().cluster().putStoredScript(put, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -60,8 +60,9 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
}
public TemplateQueryBuilder(String template, ScriptType scriptType, Map<String, Object> params, XContentType ct) {
this(new Script(scriptType, "mustache", template,
ct == null ? Collections.emptyMap() : Collections.singletonMap(Script.CONTENT_TYPE_OPTION, ct.mediaType()), params));
this(new Script(scriptType, "mustache", template, scriptType == ScriptType.INLINE ?
(ct == null ? Collections.emptyMap() : Collections.singletonMap(Script.CONTENT_TYPE_OPTION, ct.mediaType()))
: null, params));
}
TemplateQueryBuilder(Script template) {
@ -138,6 +139,13 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
public static TemplateQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
Script template = Script.parse(parser, Script.DEFAULT_TEMPLATE_LANG);
// for deprecation of stored script namespaces the default lang is ignored,
// so the template lang must be set for a stored script
if (template.getType() == ScriptType.STORED) {
template = new Script(ScriptType.STORED, Script.DEFAULT_TEMPLATE_LANG, template.getIdOrCode(), template.getParams());
}
return new TemplateQueryBuilder(template);
}
}

View File

@ -150,9 +150,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
public void testIndexedTemplateClient() throws Exception {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("testTemplate")
.setSource(new BytesArray("{" +
.setContent(new BytesArray("{" +
"\"template\":{" +
" \"query\":{" +
" \"match\":{" +
@ -163,8 +163,8 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setId("testTemplate").setSource(new BytesArray("{" +
.setLang(MustacheScriptEngineService.NAME)
.setId("testTemplate").setContent(new BytesArray("{" +
"\"template\":{" +
" \"query\":{" +
" \"match\":{" +
@ -175,7 +175,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
GetStoredScriptResponse getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get();
assertNotNull(getResponse.getStoredScript());
assertNotNull(getResponse.getSource());
BulkRequestBuilder bulkRequestBuilder = client().prepareBulk();
bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}"));
@ -200,20 +200,20 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get();
assertNull(getResponse.getStoredScript());
assertNull(getResponse.getSource());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type"))
.setScript("/template_index/mustache/1000").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get());
assertThat(e.getMessage(), containsString("Illegal index script format [/template_index/mustache/1000] should be /lang/id"));
assertThat(e.getMessage(), containsString("illegal stored script format [/template_index/mustache/1000] use only <id>"));
}
public void testIndexedTemplate() throws Exception {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("1a")
.setSource(new BytesArray("{" +
.setContent(new BytesArray("{" +
"\"template\":{" +
" \"query\":{" +
" \"match\":{" +
@ -224,9 +224,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
))
);
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("2")
.setSource(new BytesArray("{" +
.setContent(new BytesArray("{" +
"\"template\":{" +
" \"query\":{" +
" \"match\":{" +
@ -236,9 +236,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
"}"))
);
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("3")
.setSource(new BytesArray("{" +
.setContent(new BytesArray("{" +
"\"template\":{" +
" \"match\":{" +
" \"theField\" : \"{{fieldParam}}\"}" +
@ -260,7 +260,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest().indices("test").types("type"))
.setScript("/mustache/1a")
.setScript("1a")
.setScriptType(ScriptType.STORED)
.setScriptParams(templateParams)
.get();
@ -286,6 +286,8 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.setScript("/mustache/2").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get();
assertHitCount(searchResponse.getResponse(), 1);
assertWarnings("use of </lang/id> [/mustache/2] for looking up" +
" stored scripts/templates has been deprecated, use only <id> [2] instead");
Map<String, Object> vars = new HashMap<>();
vars.put("fieldParam", "bar");
@ -307,16 +309,17 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.get();
client().admin().indices().prepareRefresh().get();
int iterations = randomIntBetween(2, 11);
for (int i = 1; i < iterations; i++) {
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("git01")
.setSource(new BytesArray("{\"template\":{\"query\": {\"match_phrase_prefix\": " +
"{\"searchtext\": {\"query\": \"{{P_Keyword1}}\"," +
"\"unsupported\": \"unsupported\"}}}}}")));
.setContent(new BytesArray("{\"template\":{\"query\": {\"match\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\"," +
"\"type\": \"ooophrase_prefix\"}}}}}")));
GetStoredScriptResponse getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "git01").get();
assertNotNull(getResponse.getStoredScript());
assertNotNull(getResponse.getSource());
Map<String, Object> templateParams = new HashMap<>();
templateParams.put("P_Keyword1", "dev");
@ -325,27 +328,32 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.setRequest(new SearchRequest("testindex").types("test"))
.setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get());
assertThat(e.getMessage(), containsString("[match_phrase_prefix] query does not support [unsupported]"));
assertThat(e.getMessage(), containsString("[match] query does not support type ooophrase_prefix"));
assertWarnings("Deprecated field [type] used, replaced by [match_phrase and match_phrase_prefix query]");
assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("git01")
.setSource(new BytesArray("{\"query\": {\"match_phrase_prefix\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\"}}}}")));
.setContent(new BytesArray("{\"query\": {\"match\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\"," +
"\"type\": \"phrase_prefix\"}}}}")));
SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("testindex").types("test"))
.setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get();
assertHitCount(searchResponse.getResponse(), 1);
assertWarnings("Deprecated field [type] used, replaced by [match_phrase and match_phrase_prefix query]");
}
}
public void testIndexedTemplateWithArray() throws Exception {
String multiQuery = "{\"query\":{\"terms\":{\"theField\":[\"{{#fieldParam}}\",\"{{.}}\",\"{{/fieldParam}}\"]}}}";
assertAcked(
client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME)
.setLang(MustacheScriptEngineService.NAME)
.setId("4")
.setSource(jsonBuilder().startObject().field("template", multiQuery).endObject().bytes())
.setContent(jsonBuilder().startObject().field("template", multiQuery).endObject().bytes())
);
BulkRequestBuilder bulkRequestBuilder = client().prepareBulk();
bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}"));
@ -362,7 +370,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type"))
.setScript("/mustache/4").setScriptType(ScriptType.STORED).setScriptParams(arrayTemplateParams)
.setScript("4").setScriptType(ScriptType.STORED).setScriptParams(arrayTemplateParams)
.get();
assertHitCount(searchResponse.getResponse(), 5);
}

View File

@ -54,7 +54,7 @@
body: { "template": { "query": { "match{{}}_all": {}}, "size": "{{my_size}}" } }
- do:
catch: /Unable\sto\sparse.*/
catch: /failed\sto\sparse.*/
put_template:
id: "1"
body: { "template": { "query": { "match{{}}_all": {}}, "size": "{{my_size}}" } }

View File

@ -150,7 +150,7 @@
- match: { hits.total: 1 }
- do:
catch: /Unable.to.find.on.disk.file.script.\[simple1\].using.lang.\[mustache\]/
catch: /unable.to.find.file.script.\[simple1\].using.lang.\[mustache\]/
search_template:
body: { "file" : "simple1"}

View File

@ -52,7 +52,7 @@
warnings:
- '[template] query is deprecated, use search template api instead'
search:
body: { "query": { "template": { "stored": "/mustache/1", "params": { "my_value": "value1" } } } }
body: { "query": { "template": { "stored": "1", "params": { "my_value": "value1" } } } }
- match: { hits.total: 1 }

View File

@ -1,54 +1,47 @@
---
"Indexed script":
"Stored script":
- skip:
features: warnings
- do:
put_script:
id: "1"
lang: "painless"
body: { "script": "_score * doc['myParent.weight'].value" }
lang: "1"
body: { "script": {"lang": "painless", "code": "_score * doc['myParent.weight'].value" } }
- match: { acknowledged: true }
- do:
get_script:
id: "1"
lang: "painless"
lang: "1"
- match: { found: true }
- match: { lang: painless }
- match: { _id: "1" }
- match: { "script": "_score * doc['myParent.weight'].value" }
- match: { "script": {"lang": "painless", "code": "_score * doc['myParent.weight'].value"} }
- do:
catch: missing
get_script:
id: "2"
lang: "painless"
lang: "2"
- match: { found: false }
- match: { lang: painless }
- match: { _id: "2" }
- is_false: script
- do:
delete_script:
id: "1"
lang: "painless"
lang: "1"
- match: { acknowledged: true }
- do:
catch: missing
delete_script:
id: "non_existing"
lang: "painless"
lang: "non_existing"
- do:
catch: request
put_script:
id: "1"
lang: "painless"
body: { "script": "_score * foo bar + doc['myParent.weight'].value" }
lang: "1"
body: { "script": {"lang": "painless", "code": "_score * foo bar + doc['myParent.weight'].value"} }
- do:
catch: /compile error/
put_script:
id: "1"
lang: "painless"
body: { "script": "_score * foo bar + doc['myParent.weight'].value" }
lang: "1"
body: { "script": {"lang": "painless", "code": "_score * foo bar + doc['myParent.weight'].value"} }

View File

@ -70,3 +70,97 @@
ingest.get_pipeline:
id: "my_pipeline"
- match: { my_pipeline.description: "_description" }
---
"Test rolling upgrade for stored scripts between the old namespace and the new namespace":
- skip:
features: warnings
- do:
cluster.health:
wait_for_status: green
wait_for_nodes: 2
- do:
search:
index: stored_index
body: {
"query": {
"match_all": {
}
}
}
- match: { hits.total: 3 }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "greater"
lang: "painless"
- match: { "found": true }
- match: { "_id": "greater" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value > 1.0" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "expression"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying the field [lang] for executing stored scripts is deprecated; use only the field [stored] to specify an <id>'
search:
index: stored_index
body: {
"query": {
"script": {
"script": {
"stored": "greater",
"lang": "painless"
}
}
},
"script_fields": {
"script_painless": {
"script": {
"stored": "value",
"lang": "painless"
}
},
"script_expressions": {
"script": {
"stored": "value",
"lang": "expression"
}
}
},
"sort": {
"num": {
"order": "asc"
}
}
}
- match: { hits.total: 2 }
- match: { hits.hits.0.fields.script_painless.0: 2.0 }
- match: { hits.hits.1.fields.script_painless.0: 3.0 }
- match: { hits.hits.0.fields.script_expressions.0: 2.0 }
- match: { hits.hits.1.fields.script_expressions.0: 3.0 }

View File

@ -54,3 +54,123 @@
]
}
- match: { "acknowledged": true }
---
"Test rolling upgrade for stored scripts between the old namespace and the new namespace":
- skip:
features: warnings
- do:
indices.create:
index: stored_index
body:
settings:
index:
number_of_replicas: 0
- do:
bulk:
refresh: true
body:
- '{"index": {"_index": "stored_index", "_type": "test"}}'
- '{"value": "value1", "num": 1.0}'
- '{"index": {"_index": "stored_index", "_type": "test"}}'
- '{"value": "value2", "num": 2.0}'
- '{"index": {"_index": "stored_index", "_type": "test"}}'
- '{"value": "value3", "num": 3.0}'
- do:
indices.flush:
index: stored_index
- do:
put_script:
id: "greater"
lang: "painless"
body: {
"script": "doc['num'].value > 1.0"
}
- match: { acknowledged: true }
- do:
put_script:
id: "value"
lang: "painless"
body: {
"script": "doc['num'].value"
}
- match: { acknowledged: true }
- do:
put_script:
id: "value"
lang: "expression"
body: {
"script": "doc['num'].value"
}
- match: { acknowledged: true }
- do:
get_script:
id: "greater"
lang: "painless"
- match: { "found": true }
- match: { "_id": "greater" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value > 1.0" }
- do:
get_script:
id: "value"
lang: "painless"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value" }
- do:
get_script:
id: "value"
lang: "expression"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "expression"}
- match: { "script": "doc['num'].value" }
- do:
search:
index: stored_index
body: {
"query": {
"script": {
"script": {
"stored": "greater",
"lang": "painless"
}
}
},
"script_fields": {
"script_painless": {
"script": {
"stored": "value",
"lang": "painless"
}
},
"script_expressions": {
"script": {
"stored": "value",
"lang": "expression"
}
}
},
"sort": {
"num": {
"order": "asc"
}
}
}
- match: { hits.total: 2 }
- match: { hits.hits.0.fields.script_painless.0: 2.0 }
- match: { hits.hits.1.fields.script_painless.0: 3.0 }
- match: { hits.hits.0.fields.script_expressions.0: 2.0 }
- match: { hits.hits.1.fields.script_expressions.0: 3.0 }

View File

@ -47,3 +47,427 @@
ingest.get_pipeline:
id: "my_pipeline"
- match: { my_pipeline.description: "_description" }
---
"Test rolling upgrade for stored scripts between the old namespace and the new namespace":
- skip:
features: warnings
- do:
cluster.health:
wait_for_status: green
wait_for_nodes: 2
- do:
search:
index: stored_index
body: {
"query": {
"match_all": {
}
}
}
- match: { hits.total: 3 }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "greater"
lang: "painless"
- match: { "found": true }
- match: { "_id": "greater" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value > 1.0" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "expression"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying the field [lang] for executing stored scripts is deprecated; use only the field [stored] to specify an <id>'
search:
index: stored_index
body: {
"query": {
"script": {
"script": {
"stored": "greater",
"lang": "painless"
}
}
},
"script_fields": {
"script_painless": {
"script": {
"stored": "value",
"lang": "painless"
}
},
"script_expressions": {
"script": {
"stored": "value",
"lang": "expression"
}
}
},
"sort": {
"num": {
"order": "asc"
}
}
}
- match: { hits.total: 2 }
- match: { hits.hits.0.fields.script_painless.0: 2.0 }
- match: { hits.hits.1.fields.script_painless.0: 3.0 }
- match: { hits.hits.0.fields.script_expressions.0: 2.0 }
- match: { hits.hits.1.fields.script_expressions.0: 3.0 }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated, use request content instead'
put_script:
id: "greater"
lang: "painless"
body: {
"script": "doc['num'].value > 1.0"
}
- match: { acknowledged: true }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated, use request content instead'
put_script:
id: "value"
lang: "painless"
body: {
"script": "doc['num'].value"
}
- match: { acknowledged: true }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated, use request content instead'
- 'stored script [value] already exists using a different lang [painless], the new namespace for stored scripts will only use (id) instead of (lang, id)'
put_script:
id: "value"
lang: "expression"
body: {
"script": "doc['num'].value"
}
- match: { acknowledged: true }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "greater"
lang: "painless"
- match: { "found": true }
- match: { "_id": "greater" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value > 1.0" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "painless"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { "found": true }
- match: { "_id": "value" }
- match: { "lang": "expression"}
- match: { "script": "doc['num'].value" }
- do:
warnings:
- 'specifying the field [lang] for executing stored scripts is deprecated; use only the field [stored] to specify an <id>'
search:
index: stored_index
body: {
"query": {
"script": {
"script": {
"stored": "greater",
"lang": "painless"
}
}
},
"script_fields": {
"script_painless": {
"script": {
"stored": "value"
}
},
"script_expressions": {
"script": {
"stored": "value",
"lang": "expression"
}
}
},
"sort": {
"num": {
"order": "asc"
}
}
}
- match: { hits.total: 2 }
- match: { hits.hits.0.fields.script_painless.0: 2.0 }
- match: { hits.hits.1.fields.script_painless.0: 3.0 }
- match: { hits.hits.0.fields.script_expressions.0: 2.0 }
- match: { hits.hits.1.fields.script_expressions.0: 3.0 }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
delete_script:
id: "value"
lang: "painless"
- match: { acknowledged: true }
- do:
get_script:
lang: "value"
- match: { found: true }
- match: { _id: "value" }
- match: { script.lang: "expression"}
- match: { script.code: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "expression"}
- match: { script: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
catch: missing
get_script:
id: "value"
lang: "painless"
- match: { found: false }
- match: { _id: "value" }
- do:
warnings:
- 'stored script [value] already exists using a different lang [expression], the new namespace for stored scripts will only use (id) instead of (lang, id)'
put_script:
lang: "value"
body: {
"script": {
"code": "doc['num'].value",
"lang": "painless"
}
}
- match: { acknowledged: true }
- do:
get_script:
lang: "value"
- match: { found: true }
- match: { _id: "value" }
- match: { script.lang: "painless"}
- match: { script.code: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "expression"}
- match: { script: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "painless"}
- match: { script: "doc['num'].value" }
- do:
warnings:
- 'stored script [value] already exists using a different lang [painless], the new namespace for stored scripts will only use (id) instead of (lang, id)'
put_script:
lang: "value"
body: {
"script": {
"code": "doc['num'].value",
"lang": "expression"
}
}
- match: { acknowledged: true }
- do:
get_script:
lang: "value"
- match: { found: true }
- match: { _id: "value" }
- match: { script.lang: "expression"}
- match: { script.code: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
get_script:
id: "value"
lang: "expression"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "expression"}
- match: { script: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "painless"}
- match: { script: "doc['num'].value" }
- do:
delete_script:
lang: "value"
- match: { acknowledged: true }
- do:
catch: missing
get_script:
lang: "value"
- match: { found: false }
- match: { _id: "value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
catch: missing
get_script:
id: "value"
lang: "expression"
- match: { found: false }
- match: { _id: "value" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "painless"}
- match: { script: "doc['num'].value" }
- do:
put_script:
lang: "value"
body: {
"script": {
"code": "doc['num'].value",
"lang": "painless"
}
}
- match: { acknowledged: true }
- do:
get_script:
lang: "value"
- match: { found: true }
- match: { _id: "value" }
- match: { script.lang: "painless"}
- match: { script.code: "doc['num'].value" }
- do:
warnings:
- 'specifying lang [expression] as part of the url path is deprecated'
catch: missing
get_script:
id: "value"
lang: "expression"
- match: { found: false }
- match: { _id: "value" }
- do:
warnings:
- 'specifying lang [painless] as part of the url path is deprecated'
get_script:
id: "value"
lang: "painless"
- match: { found: true }
- match: { _id: "value" }
- match: { lang: "painless"}
- match: { script: "doc['num'].value" }
- do:
search:
index: stored_index
body: {
"query": {
"script": {
"script": {
"stored": "greater"
}
}
},
"script_fields": {
"script_painless": {
"script": {
"stored": "value"
}
}
},
"sort": {
"num": {
"order": "asc"
}
}
}
- match: { hits.total: 2 }
- match: { hits.hits.0.fields.script_painless.0: 2.0 }
- match: { hits.hits.1.fields.script_painless.0: 3.0 }

View File

@ -73,13 +73,18 @@
---
"Test script processor with stored script":
- skip:
features: warnings
- do:
put_script:
id: "sum_bytes"
lang: "painless"
lang: "sum_bytes"
body: >
{
"script" : "ctx.bytes_total = ctx.bytes_in + ctx.bytes_out"
"script": {
"lang": "painless",
"code" : "ctx.bytes_total = ctx.bytes_in + ctx.bytes_out"
}
}
- match: { acknowledged: true }

View File

@ -3,8 +3,8 @@
"documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting.html",
"methods": ["DELETE"],
"url": {
"path": "/_scripts/{lang}/{id}",
"paths": [ "/_scripts/{lang}/{id}" ],
"path": "/_scripts/{lang}",
"paths": [ "/_scripts/{lang}", "/_scripts/{lang}/{id}" ],
"parts": {
"id": {
"type" : "string",

View File

@ -3,8 +3,8 @@
"documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting.html",
"methods": ["GET"],
"url": {
"path": "/_scripts/{lang}/{id}",
"paths": [ "/_scripts/{lang}/{id}" ],
"path": "/_scripts/{lang}",
"paths": [ "/_scripts/{lang}", "/_scripts/{lang}/{id}" ],
"parts": {
"id": {
"type" : "string",

View File

@ -3,8 +3,8 @@
"documentation": "http://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting.html",
"methods": ["PUT", "POST"],
"url": {
"path": "/_scripts/{lang}/{id}",
"paths": [ "/_scripts/{lang}/{id}" ],
"path": "/_scripts/{lang}",
"paths": [ "/_scripts/{lang}", "/_scripts/{lang}/{id}" ],
"parts": {
"id": {
"type" : "string",