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="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[/\\]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[/\\]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-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[/\\]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" /> <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> { public class DeleteStoredScriptRequest extends AcknowledgedRequest<DeleteStoredScriptRequest> {
private String id; private String id;
private String scriptLang; private String lang;
DeleteStoredScriptRequest() { DeleteStoredScriptRequest() {
super();
} }
public DeleteStoredScriptRequest(String scriptLang, String id) { public DeleteStoredScriptRequest(String id, String lang) {
this.scriptLang = scriptLang; super();
this.id = id; this.id = id;
this.lang = lang;
} }
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; 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("#")) { } 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); if (lang != null && lang.contains("#")) {
} else if (scriptLang.contains("#")) { validationException = addValidationError("lang cannot contain '#' for stored script", validationException);
validationException = addValidationError("lang can't contain: '#'", validationException);
} }
return validationException; return validationException;
} }
public String scriptLang() {
return scriptLang;
}
public DeleteStoredScriptRequest scriptLang(String type) {
this.scriptLang = type;
return this;
}
public String id() { public String id() {
return id; return id;
} }
public DeleteStoredScriptRequest id(String id) { public DeleteStoredScriptRequest id(String id) {
this.id = id; this.id = id;
return this;
}
public String lang() {
return lang;
}
public DeleteStoredScriptRequest lang(String lang) {
this.lang = lang;
return this; return this;
} }
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
scriptLang = in.readString();
lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readString(); id = in.readString();
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeString(scriptLang);
out.writeString(lang == null ? "" : lang);
out.writeString(id); out.writeString(id);
} }
@Override @Override
public String toString() { 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()); super(client, action, new DeleteStoredScriptRequest());
} }
public DeleteStoredScriptRequestBuilder setScriptLang(String scriptLang) { public DeleteStoredScriptRequestBuilder setLang(String lang) {
request.scriptLang(scriptLang); request.lang(lang);
return this; return this;
} }
public DeleteStoredScriptRequestBuilder setId(String id) { public DeleteStoredScriptRequestBuilder setId(String id) {
request.id(id); request.id(id);
return this; return this;
} }

View File

@ -28,61 +28,79 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException; import java.io.IOException;
import static org.elasticsearch.action.ValidateActions.addValidationError;
public class GetStoredScriptRequest extends MasterNodeReadRequest<GetStoredScriptRequest> { public class GetStoredScriptRequest extends MasterNodeReadRequest<GetStoredScriptRequest> {
protected String id; protected String id;
protected String lang; protected String lang;
GetStoredScriptRequest() { GetStoredScriptRequest() {
super();
} }
public GetStoredScriptRequest(String lang, String id) { public GetStoredScriptRequest(String id, String lang) {
this.lang = lang; super();
this.id = id; this.id = id;
this.lang = lang;
} }
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; 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; 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() { public String id() {
return 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 @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
lang = in.readString(); lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readString(); id = in.readString();
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeString(lang);
out.writeString(lang == null ? "" : lang);
out.writeString(id); out.writeString(id);
} }

View File

@ -19,49 +19,70 @@
package org.elasticsearch.action.admin.cluster.storedscripts; package org.elasticsearch.action.admin.cluster.storedscripts;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.script.Script; import org.elasticsearch.script.StoredScriptSource;
import java.io.IOException; import java.io.IOException;
public class GetStoredScriptResponse extends ActionResponse implements ToXContent { public class GetStoredScriptResponse extends ActionResponse implements ToXContent {
private String storedScript; private StoredScriptSource source;
GetStoredScriptResponse() { GetStoredScriptResponse() {
} }
GetStoredScriptResponse(String storedScript) { GetStoredScriptResponse(StoredScriptSource source) {
this.storedScript = storedScript; this.source = source;
} }
/** /**
* @return if a stored script and if not found <code>null</code> * @return if a stored script and if not found <code>null</code>
*/ */
public String getStoredScript() { public StoredScriptSource getSource() {
return storedScript; return source;
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.value(storedScript); source.toXContent(builder, params);
return builder; return builder;
} }
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); 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 @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); 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> { public class PutStoredScriptRequest extends AcknowledgedRequest<PutStoredScriptRequest> {
private String id; private String id;
private String scriptLang; private String lang;
private BytesReference script; private BytesReference content;
public PutStoredScriptRequest() { public PutStoredScriptRequest() {
super(); super();
} }
public PutStoredScriptRequest(String scriptLang) { public PutStoredScriptRequest(String id, String lang, BytesReference content) {
super(); super();
this.scriptLang = scriptLang;
}
public PutStoredScriptRequest(String scriptLang, String id) {
super();
this.scriptLang = scriptLang;
this.id = id; this.id = id;
this.lang = lang;
this.content = content;
} }
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null; 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("#")) { } 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); if (lang != null && lang.contains("#")) {
} else if (scriptLang.contains("#")) { validationException = addValidationError("lang cannot contain '#' for stored script", validationException);
validationException = addValidationError("lang can't contain: '#'", validationException);
} }
if (script == null) {
validationException = addValidationError("script is missing", validationException); if (content == null) {
validationException = addValidationError("must specify code for stored script", validationException);
} }
return validationException; return validationException;
} }
public String scriptLang() {
return scriptLang;
}
public PutStoredScriptRequest scriptLang(String scriptLang) {
this.scriptLang = scriptLang;
return this;
}
public String id() { public String id() {
return id; return id;
} }
public PutStoredScriptRequest id(String id) { public PutStoredScriptRequest id(String id) {
this.id = id; this.id = id;
return this; return this;
} }
public BytesReference script() { public String lang() {
return script; return lang;
} }
public PutStoredScriptRequest script(BytesReference source) { public PutStoredScriptRequest lang(String lang) {
this.script = source; this.lang = lang;
return this;
}
public BytesReference content() {
return content;
}
public PutStoredScriptRequest content(BytesReference content) {
this.content = content;
return this; return this;
} }
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
scriptLang = in.readString();
lang = in.readString();
if (lang.isEmpty()) {
lang = null;
}
id = in.readOptionalString(); id = in.readOptionalString();
script = in.readBytesReference(); content = in.readBytesReference();
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeString(scriptLang);
out.writeString(lang == null ? "" : lang);
out.writeOptionalString(id); out.writeOptionalString(id);
out.writeBytesReference(script); out.writeBytesReference(content);
} }
@Override @Override
public String toString() { public String toString() {
String sSource = "_na_"; String source = "_na_";
try { try {
sSource = XContentHelper.convertToJson(script, false); source = XContentHelper.convertToJson(content, false);
} catch (Exception e) { } catch (Exception exception) {
// ignore // 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()); super(client, action, new PutStoredScriptRequest());
} }
public PutStoredScriptRequestBuilder setScriptLang(String scriptLang) {
request.scriptLang(scriptLang);
return this;
}
public PutStoredScriptRequestBuilder setId(String id) { public PutStoredScriptRequestBuilder setId(String id) {
request.id(id); request.id(id);
return this; return this;
} }
public PutStoredScriptRequestBuilder setSource(BytesReference source) { public PutStoredScriptRequestBuilder setLang(String lang) {
request.script(source); request.lang(lang);
return this; 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 @Override
protected void masterOperation(PutStoredScriptRequest request, ClusterState state, protected void masterOperation(PutStoredScriptRequest request, ClusterState state,
ActionListener<PutStoredScriptResponse> listener) throws Exception { ActionListener<PutStoredScriptResponse> listener) throws Exception {
scriptService.storeScript(clusterService, request, listener); scriptService.putStoredScript(clusterService, request, listener);
} }
@Override @Override

View File

@ -761,7 +761,7 @@ public abstract class AbstractAsyncBulkByScrollAction<Request extends AbstractBu
return request; return request;
} }
if (executable == null) { 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); executable = scriptService.executable(compiled, params);
} }
if (context == null) { if (context == null) {

View File

@ -1199,7 +1199,7 @@ public abstract class AbstractClient extends AbstractComponent implements Client
@Override @Override
public DeleteStoredScriptRequestBuilder prepareDeleteStoredScript(@Nullable String scriptLang, String id){ 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) { public final Function<Map<String, Object>, SearchScript> getLazySearchScript(Script script, ScriptContext context) {
failIfFrozen(); failIfFrozen();
CompiledScript compile = scriptService.compile(script, context, script.getOptions()); CompiledScript compile = scriptService.compile(script, context);
return (p) -> scriptService.search(lookup(), compile, p); 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) { public final Function<Map<String, Object>, ExecutableScript> getLazyExecutableScript(Script script, ScriptContext context) {
failIfFrozen(); failIfFrozen();
CompiledScript executable = scriptService.compile(script, context, script.getOptions()); CompiledScript executable = scriptService.compile(script, context);
return (p) -> scriptService.executable(executable, p); return (p) -> scriptService.executable(executable, p);
} }

View File

@ -44,10 +44,7 @@ public class InternalTemplateService implements TemplateService {
int mustacheEnd = template.indexOf("}}"); int mustacheEnd = template.indexOf("}}");
if (mustacheStart != -1 && mustacheEnd != -1 && mustacheStart < mustacheEnd) { if (mustacheStart != -1 && mustacheEnd != -1 && mustacheStart < mustacheEnd) {
Script script = new Script(ScriptType.INLINE, "mustache", template, Collections.emptyMap()); Script script = new Script(ScriptType.INLINE, "mustache", template, Collections.emptyMap());
CompiledScript compiledScript = scriptService.compile( CompiledScript compiledScript = scriptService.compile(script, ScriptContext.Standard.INGEST);
script,
ScriptContext.Standard.INGEST,
Collections.emptyMap());
return new Template() { return new Template() {
@Override @Override
public String execute(Map<String, Object> model) { 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; import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteStoredScriptAction extends BaseRestHandler { public class RestDeleteStoredScriptAction extends BaseRestHandler {
public RestDeleteStoredScriptAction(Settings settings, RestController controller) { public RestDeleteStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestDeleteStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
super(settings); super(settings);
if (registerDefaultHandlers) {
controller.registerHandler(DELETE, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptLang(RestRequest request) { // Note {lang} is actually {id} in the first handler. It appears
return request.param("lang"); // 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 @Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException { public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
DeleteStoredScriptRequest deleteStoredScriptRequest = new DeleteStoredScriptRequest(getScriptLang(request), request.param("id")); 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)); 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.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BaseRestHandler;
@ -30,57 +31,84 @@ import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener; import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.script.StoredScriptSource;
import java.io.IOException; import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.GET;
public class RestGetStoredScriptAction extends BaseRestHandler { 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) { public RestGetStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestGetStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
super(settings); super(settings);
if (registerDefaultHandlers) {
controller.registerHandler(GET, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptFieldName() { // Note {lang} is actually {id} in the first handler. It appears
return Fields.SCRIPT; // 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);
protected String getScriptLang(RestRequest request) { controller.registerHandler(GET, "/_scripts/{lang}/{id}", this);
return request.param("lang");
} }
@Override @Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException { 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) { return channel -> client.admin().cluster().getStoredScript(getRequest, new RestBuilderListener<GetStoredScriptResponse>(channel) {
@Override @Override
public RestResponse buildResponse(GetStoredScriptResponse response, XContentBuilder builder) throws Exception { public RestResponse buildResponse(GetStoredScriptResponse response, XContentBuilder builder) throws Exception {
builder.startObject(); builder.startObject();
builder.field(Fields.LANG, getRequest.lang()); builder.field(_ID_PARSE_FIELD.getPreferredName(), id);
builder.field(Fields._ID, getRequest.id());
boolean found = response.getStoredScript() != null; if (lang != null) {
builder.field(Fields.FOUND, found); builder.field(StoredScriptSource.LANG_PARSE_FIELD.getPreferredName(), lang);
RestStatus status = RestStatus.NOT_FOUND;
if (found) {
builder.field(getScriptFieldName(), response.getStoredScript());
status = RestStatus.OK;
} }
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();
} else {
builder.field(StoredScriptSource.SCRIPT_PARSE_FIELD.getPreferredName(), source.getCode());
}
}
builder.endObject(); builder.endObject();
return new BytesRestResponse(status, builder);
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.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController; 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; import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutStoredScriptAction extends BaseRestHandler { public class RestPutStoredScriptAction extends BaseRestHandler {
public RestPutStoredScriptAction(Settings settings, RestController controller) { public RestPutStoredScriptAction(Settings settings, RestController controller) {
this(settings, controller, true);
}
protected RestPutStoredScriptAction(Settings settings, RestController controller, boolean registerDefaultHandlers) {
super(settings); super(settings);
if (registerDefaultHandlers) {
controller.registerHandler(POST, "/_scripts/{lang}/{id}", this);
controller.registerHandler(PUT, "/_scripts/{lang}/{id}", this);
}
}
protected String getScriptLang(RestRequest request) { // Note {lang} is actually {id} in the first two handlers. It appears
return request.param("lang"); // 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);
} }
@Override @Override
public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException { public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
PutStoredScriptRequest putRequest = new PutStoredScriptRequest(getScriptLang(request), request.param("id")); String id = request.param("id");
putRequest.script(request.content()); 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)); return channel -> client.admin().cluster().putStoredScript(putRequest, new AcknowledgedRestListener<>(channel));
} }
} }

View File

@ -19,12 +19,15 @@
package org.elasticsearch.script; package org.elasticsearch.script;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable; 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;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
@ -42,12 +45,58 @@ import java.util.Map;
import java.util.Objects; 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} * compile and execute a script from the {@link ScriptService}
* based on the {@link ScriptType}. * 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 { 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. * 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"); "or [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
} }
if (idOrCode == null) { if (type == ScriptType.INLINE) {
throw new IllegalArgumentException("must specify an id or code for a script"); if (lang == null) {
lang = defaultLang;
}
if (idOrCode == null) {
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>");
}
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");
}
} }
if (options.size() > 1 || options.size() == 1 && options.get(CONTENT_TYPE_OPTION) == null) { return new Script(type, lang, idOrCode, options, params);
throw new IllegalArgumentException("illegal compiler options [" + options + "] specified");
}
return new Script(type, this.lang == null ? defaultLang : this.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 * @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 * the one defined by {@link Script#DEFAULT_SCRIPT_LANG} due to backwards compatiblity requirements
* related to stored queries using previously default languauges. * related to stored queries using previously default languauges.
*
* @return The parsed {@link Script}. * @return The parsed {@link Script}.
*/ */
public static Script parse(XContentParser parser, String defaultLang) throws IOException { 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. * Constructor for a script that does not need to use compiler options.
* @param type The {@link ScriptType}. * @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}. * @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}. * 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. * @param params The user-defined params to be bound for script execution.
*/ */
public Script(ScriptType type, String lang, String idOrCode, Map<String, Object> params) { 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. * Constructor for a script that requires the use of compiler options.
* @param type The {@link ScriptType}. * @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}. * @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}. * 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. * @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) { 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.type = Objects.requireNonNull(type);
this.lang = Objects.requireNonNull(lang); this.idOrCode = Objects.requireNonNull(idOrCode);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
this.params = Collections.unmodifiableMap(Objects.requireNonNull(params)); this.params = Collections.unmodifiableMap(Objects.requireNonNull(params));
if (type != ScriptType.INLINE && !options.isEmpty()) { if (type == ScriptType.INLINE) {
throw new IllegalArgumentException( this.lang = Objects.requireNonNull(lang);
"Compiler options [" + options + "] cannot be specified at runtime for [" + type + "] scripts."); 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. * Creates a {@link Script} read from an input stream.
*/ */
public Script(StreamInput in) throws IOException { public Script(StreamInput in) throws IOException {
// Version 5.1+ requires all Script members to be non-null and supports the potential // Version 5.3 allows lang to be an optional parameter for stored scripts and expects
// for more options than just XContentType. Reorders the read in contents to be in // options to be null for stored and file scripts.
// same order as the constructor. if (in.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
if (in.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
this.type = ScriptType.readFrom(in); this.type = ScriptType.readFrom(in);
this.lang = in.readString(); this.lang = in.readOptionalString();
this.idOrCode = in.readString(); this.idOrCode = in.readString();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, String> options = (Map<String, String>)(Map)in.readMap(); Map<String, String> options = (Map<String, String>)(Map)in.readMap();
this.options = options; this.options = options;
this.params = in.readMap(); this.params = in.readMap();
// Prior to version 5.1 the script members are read in certain cases as optional and given // Version 5.1 to 5.3 (exclusive) requires all Script members to be non-null and supports the potential
// default values when necessary. Also the only option supported is for XContentType. // 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 { } else {
String idOrCode = in.readString(); this.idOrCode = in.readString();
ScriptType type;
if (in.readBoolean()) { if (in.readBoolean()) {
type = ScriptType.readFrom(in); this.type = ScriptType.readFrom(in);
} else { } else {
type = DEFAULT_SCRIPT_TYPE; this.type = DEFAULT_SCRIPT_TYPE;
} }
String lang = in.readOptionalString(); String lang = in.readOptionalString();
if (lang == null) { if (lang == null) {
lang = DEFAULT_SCRIPT_LANG; this.lang = DEFAULT_SCRIPT_LANG;
} else {
this.lang = lang;
} }
Map<String, Object> params = in.readMap(); Map<String, Object> params = in.readMap();
if (params == null) { if (params == null) {
params = new HashMap<>(); this.params = new HashMap<>();
} else {
this.params = params;
} }
Map<String, String> options = new HashMap<>();
if (in.readBoolean()) { if (in.readBoolean()) {
this.options = new HashMap<>();
XContentType contentType = XContentType.readFrom(in); 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 @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
// Version 5.1+ requires all Script members to be non-null and supports the potential // Version 5.3+ allows lang to be an optional parameter for stored scripts and expects
// for more options than just XContentType. Reorders the written out contents to be in // options to be null for stored and file scripts.
// same order as the constructor. if (out.getVersion().onOrAfter(Version.V_5_3_0_UNRELEASED)) {
if (out.getVersion().onOrAfter(Version.V_5_1_1_UNRELEASED)) {
type.writeTo(out); type.writeTo(out);
out.writeString(lang); out.writeOptionalString(lang);
out.writeString(idOrCode); out.writeString(idOrCode);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>)(Map)this.options; Map<String, Object> options = (Map<String, Object>)(Map)this.options;
out.writeMap(options); out.writeMap(options);
out.writeMap(params); out.writeMap(params);
// Prior to version 5.1 the Script members were possibly written as optional or null, though this is no longer // Version 5.1 to 5.3 (exclusive) requires all Script members to be non-null and supports the potential
// necessary since Script members cannot be null anymore, and there is no case where a null value wasn't equivalent // for more options than just XContentType. Reorders the written out contents to be in
// to it's default value when actually compiling/executing a script. Meaning, there are no backwards compatibility issues, // same order as the constructor.
// and now there's enforced consistency. Also the only supported compiler option was XContentType. } 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 { } else {
out.writeString(idOrCode); out.writeString(idOrCode);
out.writeBoolean(true); out.writeBoolean(true);
type.writeTo(out); type.writeTo(out);
out.writeBoolean(true); out.writeOptionalString(lang);
out.writeString(lang);
out.writeMap(params.isEmpty() ? null : params);
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)); XContentType contentType = XContentType.fromMediaTypeOrFormat(options.get(CONTENT_TYPE_OPTION));
out.writeBoolean(true); out.writeBoolean(true);
contentType.writeTo(out); 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 * 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. * 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 { public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException {
builder.startObject(); 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)) { if (type == ScriptType.INLINE && contentType != null && builder.contentType().mediaType().equals(contentType)) {
builder.rawField(type.getParseField().getPreferredName(), new BytesArray(idOrCode)); 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); builder.field(type.getParseField().getPreferredName(), idOrCode);
} }
builder.field(LANG_PARSE_FIELD.getPreferredName(), lang); 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); builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options);
} }
@ -527,6 +692,22 @@ public final class Script implements ToXContentObject, Writeable {
return builder; 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}. * @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}. * 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}. * @return The map of compiler options for this {@link Script} if the {@link ScriptType}
*/ * is {@link ScriptType#INLINE}, {@code null} otherwise.
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}.
*/ */
public Map<String, String> getOptions() { public Map<String, String> getOptions() {
return options; return options;
@ -571,9 +739,9 @@ public final class Script implements ToXContentObject, Writeable {
Script script = (Script)o; Script script = (Script)o;
if (type != script.type) return false; 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 (!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); return params.equals(script.params);
} }
@ -581,9 +749,9 @@ public final class Script implements ToXContentObject, Writeable {
@Override @Override
public int hashCode() { public int hashCode() {
int result = type.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 + idOrCode.hashCode();
result = 31 * result + options.hashCode(); result = 31 * result + (options != null ? options.hashCode() : 0);
result = 31 * result + params.hashCode(); result = 31 * result + params.hashCode();
return result; return result;
} }

View File

@ -18,24 +18,24 @@
*/ */
package org.elasticsearch.script; package org.elasticsearch.script;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ResourceNotFoundException; 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.Diff;
import org.elasticsearch.cluster.DiffableUtils; import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.NamedDiff; import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.ParsingException; 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.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; 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.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;
import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -43,68 +43,372 @@ import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 {
/**
* 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, StoredScriptSource> 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);
}
/**
* 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);
}
}
source = scripts.get(lang + "#" + id);
if (source == null) {
throw new ResourceNotFoundException(
"stored script [" + id + "] using lang [" + lang + "] does not exist and cannot be deleted");
}
scripts.remove(lang + "#" + id);
return this;
}
/**
* @return A {@link ScriptMetaData} with the updated {@link Map} of scripts.
*/
public ScriptMetaData build() {
return new ScriptMetaData(scripts);
}
}
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
public String getWriteableName() {
return TYPE;
}
@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);
}
}
/**
* 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"; public static final String TYPE = "stored_scripts";
private final Map<String, ScriptAsBytes> 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;
ScriptMetaData(Map<String, ScriptAsBytes> scripts) { Token token = parser.currentToken();
this.scripts = scripts;
}
public BytesReference getScriptAsBytes(String language, String id) { if (token == null) {
ScriptAsBytes scriptAsBytes = scripts.get(toKey(language, id)); token = parser.nextToken();
if (scriptAsBytes != null) {
return scriptAsBytes.script;
} else {
return null;
} }
}
public String getScript(String language, String id) { if (token != Token.START_OBJECT) {
BytesReference scriptAsBytes = getScriptAsBytes(language, id); throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{]");
if (scriptAsBytes == null) {
return null;
} }
return scriptAsBytes.utf8ToString();
}
public static String parseStoredScript(BytesReference scriptAsBytes) { token = parser.nextToken();
// Scripts can be stored via API in several ways:
// 1) wrapped into a 'script' json object or field while (token != Token.END_OBJECT) {
// 2) wrapped into a 'template' json object or field switch (token) {
// 3) just as is case FIELD_NAME:
// In order to fetch the actual script in consistent manner this parsing logic is needed: id = parser.currentName();
// EMPTY is ok here because we never call namedObject, we're just copying structure. break;
try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY, scriptAsBytes); case VALUE_STRING:
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) { if (id == null) {
parser.nextToken(); throw new ParsingException(parser.getTokenLocation(),
parser.nextToken(); "unexpected token [" + token + "], expected [<id>, <code>, {]");
if (parser.currentToken() == Token.END_OBJECT) {
throw new IllegalArgumentException("Empty script");
}
switch (parser.currentName()) {
case "script":
case "template":
if (parser.nextToken() == Token.VALUE_STRING) {
return parser.text();
} else {
builder.copyCurrentStructure(parser);
} }
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; break;
default: default:
// There is no enclosing 'script' or 'template' object so we just need to return the script as is... throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [<id>, <code>, {]");
// 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) { token = parser.nextToken();
throw new RuntimeException(e);
} }
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, 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
public Diff<MetaData.Custom> diff(MetaData.Custom before) {
return new ScriptMetadataDiff((ScriptMetaData)before, this);
} }
@Override @Override
@ -112,72 +416,33 @@ public final class ScriptMetaData implements MetaData.Custom {
return TYPE; 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 @Override
public EnumSet<MetaData.XContentContext> context() { public EnumSet<MetaData.XContentContext> context() {
return MetaData.ALL_CONTEXTS; return MetaData.ALL_CONTEXTS;
} }
public ScriptMetaData(StreamInput in) throws IOException { /**
int size = in.readVInt(); * Retrieves a stored script from the new namespace if lang is {@code null}.
this.scripts = new HashMap<>(); * Otherwise, returns a stored script from the deprecated namespace. Either
for (int i = 0; i < size; i++) { * way an id is required.
String languageAndId = in.readString(); */
BytesReference script = in.readBytesReference(); StoredScriptSource getStoredScript(String id, String lang) {
scripts.put(languageAndId, new ScriptAsBytes(script)); if (lang == null) {
return scripts.get(id);
} else {
return scripts.get(lang + "#" + id);
} }
} }
@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;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(scripts.size());
for (Map.Entry<String, ScriptAsBytes> entry : scripts.entrySet()) {
out.writeString(entry.getKey());
entry.getValue().writeTo(out);
}
}
@Override
public Diff<MetaData.Custom> diff(MetaData.Custom before) {
return new ScriptMetadataDiff((ScriptMetaData) before, this);
}
public static NamedDiff<MetaData.Custom> readDiffFrom(StreamInput in) throws IOException {
return new ScriptMetadataDiff(in);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
ScriptMetaData other = (ScriptMetaData) o; ScriptMetaData that = (ScriptMetaData)o;
return scripts.equals(other.scripts);
return scripts.equals(that.scripts);
} }
@Override @Override
@ -191,112 +456,4 @@ public final class ScriptMetaData implements MetaData.Custom {
"scripts=" + scripts + "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.cluster.service.ClusterService;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.cache.RemovalListener; 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.collect.Tuple;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property; 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. * 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) { public CompiledScript compile(Script script, ScriptContext scriptContext) {
if (script == null) { Objects.requireNonNull(script);
throw new IllegalArgumentException("The parameter script (Script) must not be null."); Objects.requireNonNull(scriptContext);
}
if (scriptContext == null) {
throw new IllegalArgumentException("The parameter scriptContext (ScriptContext) must not be null.");
}
ScriptType type = script.getType();
String lang = script.getLang(); String lang = script.getLang();
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(lang); String idOrCode = script.getIdOrCode();
if (canExecuteScript(lang, script.getType(), scriptContext) == false) { Map<String, String> options = script.getOptions();
throw new IllegalStateException("scripts of type [" + script.getType() + "], operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are disabled");
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 + "]");
}
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 // TODO: fix this through some API or something, that's wrong
@ -229,10 +253,71 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
boolean notSupported = scriptContext.getKey().equals(ScriptContext.Standard.UPDATE.getKey()); boolean notSupported = scriptContext.getKey().equals(ScriptContext.Standard.UPDATE.getKey());
if (expression && notSupported) { if (expression && notSupported) {
throw new UnsupportedOperationException("scripts of type [" + script.getType() + "]," + throw new UnsupportedOperationException("scripts of type [" + script.getType() + "]," +
" operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are not supported"); " 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
} }
} }
/** public boolean isLangSupported(String lang) {
* Compiles a script straight-away, or returns the previously compiled and cached script, Objects.requireNonNull(lang);
* 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.");
}
String lang = script.getLang(); return scriptEnginesByLang.containsKey(lang);
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);
}
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) { StoredScriptSource getScriptFromClusterState(String id, String lang) {
Objects.requireNonNull(scriptLang); if (lang != null && isLangSupported(lang) == false) {
if (scriptEnginesByLang.containsKey(scriptLang) == false) { throw new IllegalArgumentException("unable to get stored script with unsupported lang [" + lang + "]");
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); ScriptMetaData scriptMetadata = clusterState.metaData().custom(ScriptMetaData.TYPE);
if (scriptMetadata == null) { 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); StoredScriptSource source = scriptMetadata.getStoredScript(id, lang);
if (script == null) {
throw new ResourceNotFoundException("Unable to find script [" + scriptLang + "/" + id + "] in cluster state"); if (source == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "]" +
(lang == null ? "" : " using lang [" + lang + "]") + " in cluster state");
} }
return script;
return source;
} }
void validateStoredScript(String id, String scriptLang, BytesReference scriptBytes) { public void putStoredScript(ClusterService clusterService, PutStoredScriptRequest request,
validateScriptSize(id, scriptBytes.length()); ActionListener<PutStoredScriptResponse> listener) {
String script = ScriptMetaData.parseStoredScript(scriptBytes); int max = SCRIPT_MAX_SIZE_IN_BYTES.get(settings);
if (Strings.hasLength(scriptBytes)) {
//Just try and compile it if (request.content().length() > max) {
try { throw new IllegalArgumentException("exceeded max allowed stored script size in bytes [" + max + "] with size [" +
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(scriptLang); request.content().length() + "] for script [" + request.id() + "]");
//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)) { StoredScriptSource source = StoredScriptSource.parse(request.lang(), request.content());
Object compiled = scriptEngineService.compile(id, script, Collections.emptyMap());
if (compiled == null) { if (isLangSupported(source.getLang()) == false) {
throw new IllegalArgumentException("Unable to parse [" + script + "] lang [" + scriptLang + throw new IllegalArgumentException("unable to put stored script with unsupported lang [" + source.getLang() + "]");
"] (ScriptService.compile returned null)"); }
}
} else { try {
logger.warn( ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(source.getLang());
"skipping compile of script [{}], lang [{}] as all scripted operations are disabled for indexed scripts",
script, scriptLang); if (isAnyScriptContextEnabled(source.getLang(), ScriptType.STORED)) {
Object compiled = scriptEngineService.compile(request.id(), source.getCode(), Collections.emptyMap());
if (compiled == null) {
throw new IllegalArgumentException("failed to parse/compile stored script [" + request.id() + "]" +
(source.getCode() == null ? "" : " using code [" + source.getCode() + "]"));
} }
} catch (ScriptException good) { } else {
// TODO: remove this when all script engines have good exceptions! throw new IllegalArgumentException(
throw good; // its already good! "cannot put stored script [" + request.id() + "], stored scripts cannot be run under any context");
} catch (Exception e) {
throw new IllegalArgumentException("Unable to parse [" + script +
"] lang [" + scriptLang + "]", e);
} }
} else { } catch (ScriptException good) {
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) { clusterService.submitStateUpdateTask("put-script-" + request.id(),
String scriptLang = validateScriptLanguage(request.scriptLang()); new AckedClusterStateUpdateTask<PutStoredScriptResponse>(request, listener) {
//verify that the script compiles
validateStoredScript(request.id(), scriptLang, request.script());
clusterService.submitStateUpdateTask("put-script-" + request.id(), new AckedClusterStateUpdateTask<PutStoredScriptResponse>(request, listener) {
@Override @Override
protected PutStoredScriptResponse newResponse(boolean acknowledged) { protected PutStoredScriptResponse newResponse(boolean acknowledged) {
@ -421,23 +425,23 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
@Override @Override
public ClusterState execute(ClusterState currentState) throws Exception { 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) { public void deleteStoredScript(ClusterService clusterService, DeleteStoredScriptRequest request,
ScriptMetaData scriptMetadata = currentState.metaData().custom(ScriptMetaData.TYPE); ActionListener<DeleteStoredScriptResponse> listener) {
ScriptMetaData.Builder scriptMetadataBuilder = new ScriptMetaData.Builder(scriptMetadata); if (request.lang() != null && isLangSupported(request.lang()) == false) {
scriptMetadataBuilder.storeScript(validatedScriptLang, request.id(), request.script()); throw new IllegalArgumentException("unable to delete stored script with unsupported lang [" + request.lang() +"]");
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) { clusterService.submitStateUpdateTask("delete-script-" + request.id(),
String scriptLang = validateScriptLanguage(request.scriptLang()); new AckedClusterStateUpdateTask<DeleteStoredScriptResponse>(request, listener) {
clusterService.submitStateUpdateTask("delete-script-" + request.id(), new AckedClusterStateUpdateTask<DeleteStoredScriptResponse>(request, listener) {
@Override @Override
protected DeleteStoredScriptResponse newResponse(boolean acknowledged) { protected DeleteStoredScriptResponse newResponse(boolean acknowledged) {
@ -446,24 +450,20 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
@Override @Override
public ClusterState execute(ClusterState currentState) throws Exception { 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) { public StoredScriptSource getStoredScript(ClusterState state, GetStoredScriptRequest 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) {
ScriptMetaData scriptMetadata = state.metaData().custom(ScriptMetaData.TYPE); ScriptMetaData scriptMetadata = state.metaData().custom(ScriptMetaData.TYPE);
if (scriptMetadata != null) { if (scriptMetadata != null) {
return scriptMetadata.getScript(request.lang(), request.id()); return scriptMetadata.getStoredScript(request.id(), request.lang());
} else { } else {
return null; return null;
} }
@ -473,7 +473,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
* Compiles (or retrieves from cache) and executes the provided script * Compiles (or retrieves from cache) and executes the provided script
*/ */
public ExecutableScript executable(Script script, ScriptContext scriptContext) { 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 * Compiles (or retrieves from cache) and executes the provided search script
*/ */
public SearchScript search(SearchLookup lookup, Script script, ScriptContext scriptContext) { 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()); return search(lookup, compiledScript, script.getParams());
} }
@ -520,18 +520,6 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
return scriptMetrics.stats(); 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 @Override
public void clusterChanged(ClusterChangedEvent event) { public void clusterChanged(ClusterChangedEvent event) {
clusterState = event.state(); clusterState = event.state();
@ -592,11 +580,11 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
logger.info("compiling script file [{}]", file.toAbsolutePath()); logger.info("compiling script file [{}]", file.toAbsolutePath());
try (InputStreamReader reader = new InputStreamReader(Files.newInputStream(file), StandardCharsets.UTF_8)) { try (InputStreamReader reader = new InputStreamReader(Files.newInputStream(file), StandardCharsets.UTF_8)) {
String script = Streams.copyToString(reader); String script = Streams.copyToString(reader);
String name = scriptNameExt.v1(); String id = scriptNameExt.v1();
CacheKey cacheKey = new CacheKey(engineService, name, null, Collections.emptyMap()); CacheKey cacheKey = new CacheKey(engineService.getType(), id, null);
// pass the actual file name to the compiler (for script engines that care about this) // 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()); 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); staticCache.put(cacheKey, compiledScript);
scriptMetrics.onCompilation(); scriptMetrics.onCompilation();
} }
@ -637,7 +625,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
ScriptEngineService engineService = getScriptEngineServiceForFileExt(scriptNameExt.v2()); ScriptEngineService engineService = getScriptEngineServiceForFileExt(scriptNameExt.v2());
assert engineService != null; assert engineService != null;
logger.info("removing script file [{}]", file.toAbsolutePath()); 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 { private static final class CacheKey {
final String lang; final String lang;
final String name; final String idOrCode;
final String code; final Map<String, String> options;
final Map<String, String> params;
private CacheKey(final ScriptEngineService service, final String name, final String code, final Map<String, String> params) { private CacheKey(String lang, String idOrCode, Map<String, String> options) {
this.lang = service.getType(); this.lang = lang;
this.name = name; this.idOrCode = idOrCode;
this.code = code; this.options = options;
this.params = params;
} }
@Override @Override
@ -668,44 +654,18 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
CacheKey cacheKey = (CacheKey)o; CacheKey cacheKey = (CacheKey)o;
if (!lang.equals(cacheKey.lang)) return false; if (lang != null ? !lang.equals(cacheKey.lang) : cacheKey.lang != null) return false;
if (name != null ? !name.equals(cacheKey.name) : cacheKey.name != null) return false; if (!idOrCode.equals(cacheKey.idOrCode)) return false;
if (code != null ? !code.equals(cacheKey.code) : cacheKey.code != null) return false; return options != null ? options.equals(cacheKey.options) : cacheKey.options == null;
return params.equals(cacheKey.params);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = lang.hashCode(); int result = lang != null ? lang.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + idOrCode.hashCode();
result = 31 * result + (code != null ? code.hashCode() : 0); result = 31 * result + (options != null ? options.hashCode() : 0);
result = 31 * result + params.hashCode();
return result; 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) { if (firstAggregation.reduceScript.getParams() != null) {
vars.putAll(firstAggregation.reduceScript.getParams()); vars.putAll(firstAggregation.reduceScript.getParams());
} }
CompiledScript compiledScript = reduceContext.scriptService().compile(firstAggregation.reduceScript, CompiledScript compiledScript = reduceContext.scriptService().compile(
ScriptContext.Standard.AGGS, Collections.emptyMap()); firstAggregation.reduceScript, ScriptContext.Standard.AGGS);
ExecutableScript script = reduceContext.scriptService().executable(compiledScript, vars); ExecutableScript script = reduceContext.scriptService().executable(compiledScript, vars);
aggregation = script.run(); aggregation = script.run();
} else { } else {

View File

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

View File

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

View File

@ -415,7 +415,8 @@ public class InnerHitBuilderTests extends ESTestCase {
randomMap.put(String.valueOf(i), randomAsciiOfLength(16)); 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()); 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(); .put("script.engine." + MockScriptEngine.NAME + ".file.aggs", "false").build();
ScriptService scriptService = makeScriptService(settings); ScriptService scriptService = makeScriptService(settings);
Script script = new Script(ScriptType.FILE, MockScriptEngine.NAME, "script1", Collections.emptyMap()); 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); assertNotNull(compiledScript);
MockCompiledScript executable = (MockCompiledScript) compiledScript.compiled(); MockCompiledScript executable = (MockCompiledScript) compiledScript.compiled();
assertEquals("script1.mockscript", executable.getName()); 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()); Script script = new Script(ScriptType.FILE, MockScriptEngine.NAME, "script1", Collections.emptyMap());
for (ScriptContext context : ScriptContext.Standard.values()) { for (ScriptContext context : ScriptContext.Standard.values()) {
try { try {
scriptService.compile(script, context, Collections.emptyMap()); scriptService.compile(script, context);
fail(context.getKey() + " script should have been rejected"); fail(context.getKey() + " script should have been rejected");
} catch(Exception e) { } catch(Exception e) {
assertTrue(e.getMessage(), e.getMessage().contains("scripts of type [file], operation [" + context.getKey() + "] and lang [" + MockScriptEngine.NAME + "] are disabled")); 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()) { for (ScriptContext scriptContext : scriptContextRegistry.scriptContexts()) {
assertThat(scriptService.compile(new Script(ScriptType.INLINE, NativeScriptEngineService.NAME, "my", Collections.emptyMap()), 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; 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.common.settings.Settings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
@ -50,8 +54,16 @@ public class ScriptContextTests extends ESTestCase {
new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op")); new ScriptContext.Plugin(PLUGIN_NAME, "custom_globally_disabled_op"));
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(customContexts); ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(customContexts);
ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry); 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 { public void testCustomGlobalScriptContextSettings() throws Exception {
@ -59,7 +71,7 @@ public class ScriptContextTests extends ESTestCase {
for (ScriptType scriptType : ScriptType.values()) { for (ScriptType scriptType : ScriptType.values()) {
try { try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap()); 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"); fail("script compilation should have been rejected");
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("scripts of type [" + scriptType + "], operation [" + PLUGIN_NAME + "_custom_globally_disabled_op] and lang [" + MockScriptEngine.NAME + "] are disabled")); 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(); ScriptService scriptService = makeScriptService();
Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap()); Script script = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "1", Collections.emptyMap());
try { 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"); fail("script compilation should have been rejected");
} catch (IllegalStateException e) { } 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")); 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 // still works for other script contexts
assertNotNull(scriptService.compile(script, ScriptContext.Standard.AGGS, Collections.emptyMap())); assertNotNull(scriptService.compile(script, ScriptContext.Standard.AGGS));
assertNotNull(scriptService.compile(script, ScriptContext.Standard.SEARCH, Collections.emptyMap())); assertNotNull(scriptService.compile(script, ScriptContext.Standard.SEARCH));
assertNotNull(scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_op"), Collections.emptyMap())); assertNotNull(scriptService.compile(script, new ScriptContext.Plugin(PLUGIN_NAME, "custom_op")));
} }
public void testUnknownPluginScriptContext() throws Exception { public void testUnknownPluginScriptContext() throws Exception {
@ -88,7 +100,7 @@ public class ScriptContextTests extends ESTestCase {
for (ScriptType scriptType : ScriptType.values()) { for (ScriptType scriptType : ScriptType.values()) {
try { try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap()); 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"); fail("script compilation should have been rejected");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains("script context [" + PLUGIN_NAME + "_unknown] not supported")); 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()) { for (ScriptType scriptType : ScriptType.values()) {
try { try {
Script script = new Script(scriptType, MockScriptEngine.NAME, "1", Collections.emptyMap()); 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"); fail("script compilation should have been rejected");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage(), e.getMessage().contains("script context [test] not supported")); 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.cluster.DiffableUtils;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.InputStreamStreamInput; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.io.stream.OutputStreamStreamOutput;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType; 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.io.IOException;
import java.util.Collections; import java.io.UncheckedIOException;
public class ScriptMetaDataTests extends AbstractSerializingTestCase<ScriptMetaData> {
public class ScriptMetaDataTests extends ESTestCase {
public void testGetScript() throws Exception { public void testGetScript() throws Exception {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null); ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
XContentBuilder sourceBuilder = XContentFactory.jsonBuilder(); XContentBuilder sourceBuilder = XContentFactory.jsonBuilder();
sourceBuilder.startObject().startObject("template").field("field", "value").endObject().endObject(); 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 = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("template", "value").endObject(); 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 = XContentFactory.jsonBuilder();
sourceBuilder.startObject().startObject("script").field("field", "value").endObject().endObject(); 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 = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("script", "value").endObject(); 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 = XContentFactory.jsonBuilder();
sourceBuilder.startObject().field("field", "value").endObject(); sourceBuilder.startObject().field("field", "value").endObject();
builder.storeScript("lang", "any", sourceBuilder.bytes()); builder.storeScript("any", StoredScriptSource.parse("lang", sourceBuilder.bytes()));
ScriptMetaData scriptMetaData = builder.build(); ScriptMetaData scriptMetaData = builder.build();
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "template")); assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("template", "lang").getCode());
assertEquals("value", scriptMetaData.getScript("lang", "template_field")); assertEquals("value", scriptMetaData.getStoredScript("template_field", "lang").getCode());
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "script")); assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("script", "lang").getCode());
assertEquals("value", scriptMetaData.getScript("lang", "script_field")); assertEquals("value", scriptMetaData.getStoredScript("script_field", "lang").getCode());
assertEquals("{\"field\":\"value\"}", scriptMetaData.getScript("lang", "any")); assertEquals("{\"field\":\"value\"}", scriptMetaData.getStoredScript("any", "lang").getCode());
}
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());
} }
public void testDiff() throws Exception { public void testDiff() throws Exception {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null); ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("lang", "1", new BytesArray("{\"foo\":\"abc\"}")); builder.storeScript("1", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"abc\"}")));
builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"def\"}")); builder.storeScript("2", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"def\"}")));
builder.storeScript("lang", "3", new BytesArray("{\"foo\":\"ghi\"}")); builder.storeScript("3", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"ghi\"}")));
ScriptMetaData scriptMetaData1 = builder.build(); ScriptMetaData scriptMetaData1 = builder.build();
builder = new ScriptMetaData.Builder(scriptMetaData1); builder = new ScriptMetaData.Builder(scriptMetaData1);
builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"changed\"}")); builder.storeScript("2", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"changed\"}")));
builder.deleteScript("lang", "3"); builder.deleteScript("3", "lang");
builder.storeScript("lang", "4", new BytesArray("{\"foo\":\"jkl\"}")); builder.storeScript("4", StoredScriptSource.parse("lang", new BytesArray("{\"foo\":\"jkl\"}")));
ScriptMetaData scriptMetaData2 = builder.build(); ScriptMetaData scriptMetaData2 = builder.build();
ScriptMetaData.ScriptMetadataDiff diff = (ScriptMetaData.ScriptMetadataDiff) scriptMetaData2.diff(scriptMetaData1); ScriptMetaData.ScriptMetadataDiff diff = (ScriptMetaData.ScriptMetadataDiff) scriptMetaData2.diff(scriptMetaData1);
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().size()); assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().size());
assertEquals("lang#3", ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().get(0)); assertEquals("3", ((DiffableUtils.MapDiff) diff.pipelines).getDeletes().get(0));
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getDiffs().size()); assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getDiffs().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getDiffs().get("lang#2")); assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getDiffs().get("2"));
assertEquals(1, ((DiffableUtils.MapDiff) diff.pipelines).getUpserts().size()); assertEquals(2, ((DiffableUtils.MapDiff) diff.pipelines).getUpserts().size());
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("lang#4")); assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("4"));
ScriptMetaData result = (ScriptMetaData) diff.apply(scriptMetaData1); ScriptMetaData result = (ScriptMetaData) diff.apply(scriptMetaData1);
assertEquals(new BytesArray("{\"foo\":\"abc\"}"), result.getScriptAsBytes("lang", "1")); assertEquals("{\"foo\":\"abc\"}", result.getStoredScript("1", "lang").getCode());
assertEquals(new BytesArray("{\"foo\":\"changed\"}"), result.getScriptAsBytes("lang", "2")); assertEquals("{\"foo\":\"changed\"}", result.getStoredScript("2", "lang").getCode());
assertEquals(new BytesArray("{\"foo\":\"jkl\"}"), result.getScriptAsBytes("lang", "4")); assertEquals("{\"foo\":\"jkl\"}", result.getStoredScript("4", "lang").getCode());
} }
public void testBuilder() { public void testBuilder() {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null); ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("_lang", "_id", new BytesArray("{\"script\":\"1 + 1\"}")); builder.storeScript("_id", StoredScriptSource.parse("_lang", 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());
ScriptMetaData result = builder.build(); 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 { private ScriptMetaData randomScriptMetaData(XContentType sourceContentType) throws IOException {
@ -146,10 +105,44 @@ public class ScriptMetaDataTests extends ESTestCase {
for (int i = 0; i < numScripts; i++) { for (int i = 0; i < numScripts; i++) {
String lang = randomAsciiOfLength(4); String lang = randomAsciiOfLength(4);
XContentBuilder sourceBuilder = XContentBuilder.builder(sourceContentType.xContent()); XContentBuilder sourceBuilder = XContentBuilder.builder(sourceContentType.xContent());
sourceBuilder.startObject().field(randomAsciiOfLength(4), randomAsciiOfLength(4)).endObject(); sourceBuilder.startObject().field("script", randomAsciiOfLength(4)).endObject();
builder.storeScript(lang, randomAsciiOfLength(i + 1), sourceBuilder.bytes()); builder.storeScript(randomAsciiOfLength(i + 1), StoredScriptSource.parse(lang, sourceBuilder.bytes()));
} }
return builder.build(); 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; package org.elasticsearch.script;
import org.elasticsearch.ResourceNotFoundException; 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.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
@ -121,9 +119,9 @@ public class ScriptServiceTests extends ESTestCase {
// TODO: // TODO:
scriptService = new ScriptService(finalSettings, environment, resourceWatcherService, scriptEngineRegistry, scriptContextRegistry, scriptSettings) { scriptService = new ScriptService(finalSettings, environment, resourceWatcherService, scriptEngineRegistry, scriptContextRegistry, scriptSettings) {
@Override @Override
String getScriptFromClusterState(String scriptLang, String id) { StoredScriptSource getScriptFromClusterState(String id, String lang) {
//mock the script that gets retrieved from an index //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(); resourceWatcherService.notifyNow();
CompiledScript compiledScript = scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()), 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")); assertThat(compiledScript.compiled(), equalTo((Object) "compiled_test_file"));
Files.delete(testFileNoExt); Files.delete(testFileNoExt);
@ -178,11 +176,10 @@ public class ScriptServiceTests extends ESTestCase {
resourceWatcherService.notifyNow(); resourceWatcherService.notifyNow();
try { try {
scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()), ScriptContext.Standard.SEARCH, scriptService.compile(new Script(ScriptType.FILE, "test", "test_script", Collections.emptyMap()), ScriptContext.Standard.SEARCH);
Collections.emptyMap());
fail("the script test_script should no longer exist"); fail("the script test_script should no longer exist");
} catch (IllegalArgumentException ex) { } 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(); resourceWatcherService.notifyNow();
CompiledScript compiledScript = scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()), 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")); assertThat(compiledScript.compiled(), equalTo((Object) "compiled_test_file_script"));
Files.delete(testHiddenFile); Files.delete(testHiddenFile);
@ -208,9 +205,9 @@ public class ScriptServiceTests extends ESTestCase {
public void testInlineScriptCompiledOnceCache() throws IOException { public void testInlineScriptCompiledOnceCache() throws IOException {
buildScriptService(Settings.EMPTY); buildScriptService(Settings.EMPTY);
CompiledScript compiledScript1 = scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), 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()), 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())); assertThat(compiledScript1.compiled(), sameInstance(compiledScript2.compiled()));
} }
@ -333,7 +330,7 @@ public class ScriptServiceTests extends ESTestCase {
String type = scriptEngineService.getType(); String type = scriptEngineService.getType();
try { try {
scriptService.compile(new Script(randomFrom(ScriptType.values()), type, "test", Collections.emptyMap()), 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"); fail("script compilation should have been rejected");
} catch(IllegalArgumentException e) { } catch(IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("script context [" + pluginName + "_" + unknownContext + "] not supported")); assertThat(e.getMessage(), containsString("script context [" + pluginName + "_" + unknownContext + "] not supported"));
@ -342,8 +339,7 @@ public class ScriptServiceTests extends ESTestCase {
public void testCompileCountedInCompilationStats() throws IOException { public void testCompileCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY); buildScriptService(Settings.EMPTY);
scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), randomFrom(scriptContexts), scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), randomFrom(scriptContexts));
Collections.emptyMap());
assertEquals(1L, scriptService.stats().getCompilations()); assertEquals(1L, scriptService.stats().getCompilations());
} }
@ -364,8 +360,7 @@ public class ScriptServiceTests extends ESTestCase {
int numberOfCompilations = randomIntBetween(1, 1024); int numberOfCompilations = randomIntBetween(1, 1024);
for (int i = 0; i < numberOfCompilations; i++) { for (int i = 0; i < numberOfCompilations; i++) {
scriptService scriptService
.compile(new Script(ScriptType.INLINE, "test", i + " + " + i, Collections.emptyMap()), randomFrom(scriptContexts), .compile(new Script(ScriptType.INLINE, "test", i + " + " + i, Collections.emptyMap()), randomFrom(scriptContexts));
Collections.emptyMap());
} }
assertEquals(numberOfCompilations, scriptService.stats().getCompilations()); assertEquals(numberOfCompilations, scriptService.stats().getCompilations());
} }
@ -383,15 +378,13 @@ public class ScriptServiceTests extends ESTestCase {
public void testFileScriptCountedInCompilationStats() throws IOException { public void testFileScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY); buildScriptService(Settings.EMPTY);
createFileScripts("test"); createFileScripts("test");
scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()), randomFrom(scriptContexts), scriptService.compile(new Script(ScriptType.FILE, "test", "file_script", Collections.emptyMap()), randomFrom(scriptContexts));
Collections.emptyMap());
assertEquals(1L, scriptService.stats().getCompilations()); assertEquals(1L, scriptService.stats().getCompilations());
} }
public void testIndexedScriptCountedInCompilationStats() throws IOException { public void testIndexedScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY); buildScriptService(Settings.EMPTY);
scriptService.compile(new Script(ScriptType.STORED, "test", "script", Collections.emptyMap()), randomFrom(scriptContexts), scriptService.compile(new Script(ScriptType.STORED, "test", "script", Collections.emptyMap()), randomFrom(scriptContexts));
Collections.emptyMap());
assertEquals(1L, scriptService.stats().getCompilations()); assertEquals(1L, scriptService.stats().getCompilations());
} }
@ -411,8 +404,7 @@ public class ScriptServiceTests extends ESTestCase {
builder.put("script.inline", "true"); builder.put("script.inline", "true");
buildScriptService(builder.build()); buildScriptService(builder.build());
CompiledScript script = scriptService.compile( CompiledScript script = scriptService.compile(
new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, "1 + 1", Collections.emptyMap()), new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, "1 + 1", Collections.emptyMap()), randomFrom(scriptContexts));
randomFrom(scriptContexts), Collections.emptyMap());
assertEquals(script.lang(), Script.DEFAULT_SCRIPT_LANG); assertEquals(script.lang(), Script.DEFAULT_SCRIPT_LANG);
} }
@ -421,68 +413,41 @@ public class ScriptServiceTests extends ESTestCase {
.field("script", "abc") .field("script", "abc")
.endObject().bytes(); .endObject().bytes();
ClusterState empty = ClusterState.builder(new ClusterName("_name")).build(); ScriptMetaData scriptMetaData = ScriptMetaData.putStoredScript(null, "_id", StoredScriptSource.parse("_lang", script));
PutStoredScriptRequest request = new PutStoredScriptRequest("_lang", "_id")
.script(script);
ClusterState result = ScriptService.innerStoreScript(empty, "_lang", request);
ScriptMetaData scriptMetaData = result.getMetaData().custom(ScriptMetaData.TYPE);
assertNotNull(scriptMetaData); assertNotNull(scriptMetaData);
assertEquals("abc", scriptMetaData.getScript("_lang", "_id")); assertEquals("abc", scriptMetaData.getStoredScript("_id", "_lang").getCode());
} }
public void testDeleteScript() throws Exception { public void testDeleteScript() throws Exception {
ClusterState cs = ClusterState.builder(new ClusterName("_name")) ScriptMetaData scriptMetaData = ScriptMetaData.putStoredScript(null, "_id",
.metaData(MetaData.builder() StoredScriptSource.parse("_lang", new BytesArray("{\"script\":\"abc\"}")));
.putCustom(ScriptMetaData.TYPE, scriptMetaData = ScriptMetaData.deleteStoredScript(scriptMetaData, "_id", "_lang");
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);
assertNotNull(scriptMetaData); assertNotNull(scriptMetaData);
assertNull(scriptMetaData.getScript("_lang", "_id")); assertNull(scriptMetaData.getStoredScript("_id", "_lang"));
assertNull(scriptMetaData.getScriptAsBytes("_lang", "_id"));
ScriptMetaData errorMetaData = scriptMetaData;
ResourceNotFoundException e = expectThrows(ResourceNotFoundException.class, () -> { 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 { public void testGetStoredScript() throws Exception {
buildScriptService(Settings.EMPTY); buildScriptService(Settings.EMPTY);
ClusterState cs = ClusterState.builder(new ClusterName("_name")) ClusterState cs = ClusterState.builder(new ClusterName("_name"))
.metaData(MetaData.builder() .metaData(MetaData.builder()
.putCustom(ScriptMetaData.TYPE, .putCustom(ScriptMetaData.TYPE,
new ScriptMetaData.Builder(null).storeScript("_lang", "_id", new ScriptMetaData.Builder(null).storeScript("_id",
new BytesArray("{\"script\":\"abc\"}")).build())) StoredScriptSource.parse("_lang", new BytesArray("{\"script\":\"abc\"}"))).build()))
.build(); .build();
assertEquals("abc", scriptService.getStoredScript(cs, new GetStoredScriptRequest("_lang", "_id"))); assertEquals("abc", scriptService.getStoredScript(cs, new GetStoredScriptRequest("_id", "_lang")).getCode());
assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_lang", "_id2"))); assertNull(scriptService.getStoredScript(cs, new GetStoredScriptRequest("_id2", "_lang")));
cs = ClusterState.builder(new ClusterName("_name")).build(); 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 { private void createFileScripts(String... langs) throws IOException {
for (String lang : langs) { for (String lang : langs) {
Path scriptPath = scriptsFilePath.resolve("file_script." + lang); 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) { private void assertCompileRejected(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) {
try { 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 + "]"); fail("compile should have been rejected for lang [" + lang + "], script_type [" + scriptType + "], scripted_op [" + scriptContext + "]");
} catch(IllegalStateException e) { } catch(IllegalStateException e) {
//all good //all good
@ -502,7 +467,7 @@ public class ScriptServiceTests extends ESTestCase {
private void assertCompileAccepted(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) { private void assertCompileAccepted(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) {
assertThat( assertThat(
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext, Collections.emptyMap()), scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext),
notNullValue() notNullValue()
); );
} }

View File

@ -78,10 +78,9 @@ public class ScriptTests extends ESTestCase {
} }
return new Script( return new Script(
scriptType, scriptType,
randomFrom("_lang1", "_lang2", "_lang3"), scriptType == ScriptType.STORED ? null : randomFrom("_lang1", "_lang2", "_lang3"),
script, script,
scriptType == ScriptType.INLINE ? scriptType == ScriptType.INLINE ? Collections.singletonMap(Script.CONTENT_TYPE_OPTION, xContent.type().mediaType()) : null,
Collections.singletonMap(Script.CONTENT_TYPE_OPTION, xContent.type().mediaType()) : Collections.emptyMap(),
params 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() { public void testBasics() {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(LANG) .setLang(LANG)
.setId("foobar") .setId("foobar")
.setSource(new BytesArray("{\"script\":\"1\"}"))); .setContent(new BytesArray("{\"script\":\"1\"}")));
String script = client().admin().cluster().prepareGetStoredScript(LANG, "foobar") String script = client().admin().cluster().prepareGetStoredScript(LANG, "foobar")
.get().getStoredScript(); .get().getSource().getCode();
assertNotNull(script); assertNotNull(script);
assertEquals("1", script); assertEquals("1", script);
assertAcked(client().admin().cluster().prepareDeleteStoredScript() assertAcked(client().admin().cluster().prepareDeleteStoredScript()
.setId("foobar") .setId("foobar")
.setScriptLang(LANG)); .setLang(LANG));
script = client().admin().cluster().prepareGetStoredScript(LANG, "foobar") StoredScriptSource source = client().admin().cluster().prepareGetStoredScript(LANG, "foobar")
.get().getStoredScript(); .get().getSource();
assertNull(script); assertNull(source);
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript() IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript()
.setScriptLang("lang#") .setLang("lang#")
.setId("id#") .setId("id#")
.setSource(new BytesArray("{}")) .setContent(new BytesArray("{}"))
.get()); .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() { public void testMaxScriptSize() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript() IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().preparePutStoredScript()
.setScriptLang(LANG) .setLang(LANG)
.setId("foobar") .setId("foobar")
.setSource(new BytesArray(randomAsciiOfLength(SCRIPT_MAX_SIZE_IN_BYTES + 1))) .setContent(new BytesArray(randomAsciiOfLength(SCRIPT_MAX_SIZE_IN_BYTES + 1)))
.get() .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 { 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 // 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 // must match a predefined script from CustomScriptPlugin.pluginScripts() method
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
.setId("initScript_stored") .setId("initScript_stored")
.setSource(new BytesArray("{\"script\":\"vars.multiplier = 3\"}"))); .setContent(new BytesArray("{\"script\":\"vars.multiplier = 3\"}")));
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
.setId("mapScript_stored") .setId("mapScript_stored")
.setSource(new BytesArray("{\"script\":\"_agg.add(vars.multiplier)\"}"))); .setContent(new BytesArray("{\"script\":\"_agg.add(vars.multiplier)\"}")));
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
.setId("combineScript_stored") .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() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
.setId("reduceScript_stored") .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); indexRandom(true, builders);
ensureSearchable(); ensureSearchable();

View File

@ -55,8 +55,9 @@ public class ScriptedMetricTests extends BaseAggregationTestCase<ScriptedMetricA
if (randomBoolean()) { if (randomBoolean()) {
return new Script(script); return new Script(script);
} else { } else {
ScriptType type = randomFrom(ScriptType.values());
return new Script( 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() { public void testStoredScript() {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setId("my_script") .setId("my_script")
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
// Script source is not interpreted but it references a pre-defined script from CustomScriptPlugin // 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() SearchResponse response = client()
.prepareSearch("idx") .prepareSearch("idx")

View File

@ -46,7 +46,9 @@ public class BucketScriptTests extends BasePipelineAggregationTestCase<BucketScr
if (randomBoolean()) { if (randomBoolean()) {
params.put("foo", "bar"); 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); BucketScriptPipelineAggregationBuilder factory = new BucketScriptPipelineAggregationBuilder(name, bucketsPaths, script);
if (randomBoolean()) { if (randomBoolean()) {

View File

@ -432,9 +432,9 @@ public class BucketSelectorIT extends ESIntegTestCase {
public void testStoredScript() { public void testStoredScript() {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setId("my_script") .setId("my_script")
.setScriptLang(CustomScriptPlugin.NAME) .setLang(CustomScriptPlugin.NAME)
// Source is not interpreted but my_script is defined in CustomScriptPlugin // 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()); 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()) { if (randomBoolean()) {
params.put("foo", "bar"); 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); BucketSelectorPipelineAggregationBuilder factory = new BucketSelectorPipelineAggregationBuilder(name, bucketsPaths, script);
if (randomBoolean()) { if (randomBoolean()) {

View File

@ -97,7 +97,7 @@ public abstract class AbstractSortTestCase<T extends SortBuilder<T>> extends EST
scriptService = new ScriptService(baseSettings, environment, scriptService = new ScriptService(baseSettings, environment,
new ResourceWatcherService(baseSettings, null), scriptEngineRegistry, scriptContextRegistry, scriptSettings) { new ResourceWatcherService(baseSettings, null), scriptEngineRegistry, scriptContextRegistry, scriptSettings) {
@Override @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); return new CompiledScript(ScriptType.INLINE, "mockName", "test", script);
} }
}; };

View File

@ -539,9 +539,9 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
if(testScript) { if(testScript) {
logger.info("--> creating test script"); logger.info("--> creating test script");
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MockScriptEngine.NAME) .setLang(MockScriptEngine.NAME)
.setId("foobar") .setId("foobar")
.setSource(new BytesArray("{\"script\":\"1\"}"))); .setContent(new BytesArray("{\"script\":\"1\"}")));
} }
logger.info("--> snapshot without global state"); logger.info("--> snapshot without global state");
@ -600,7 +600,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
if (testScript) { if (testScript) {
logger.info("--> check that script is restored"); logger.info("--> check that script is restored");
GetStoredScriptResponse getStoredScriptResponse = client().admin().cluster().prepareGetStoredScript(MockScriptEngine.NAME, "foobar").get(); GetStoredScriptResponse getStoredScriptResponse = client().admin().cluster().prepareGetStoredScript(MockScriptEngine.NAME, "foobar").get();
assertNotNull(getStoredScriptResponse.getStoredScript()); assertNotNull(getStoredScriptResponse.getSource());
} }
createIndex("test-idx"); createIndex("test-idx");
@ -644,7 +644,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
getIndexTemplatesResponse = client().admin().indices().prepareGetTemplates().get(); getIndexTemplatesResponse = client().admin().indices().prepareGetTemplates().get();
assertIndexTemplateMissing(getIndexTemplatesResponse, "test-template"); assertIndexTemplateMissing(getIndexTemplatesResponse, "test-template");
assertFalse(client().admin().cluster().prepareGetPipeline("barbaz").get().isFound()); 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)); 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. 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 Specifies the source of the script. An `inline` script is specified
`inline` as in the example above, a stored script with the specified `id` `inline` as in the example above, a `stored` script is specified `stored`
is retrieved from the cluster state (see <<modules-scripting-stored-scripts,Stored Scripts>>), 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` and a `file` script is retrieved from a file in the `config/scripts`
directory (see <<modules-scripting-file-scripts, File 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` script in `group1/group2/my_script.painless` should use `group1_group2_myscript`
as the `file` name. as the `file` name.
[[reload-scripts]] [[reload-scripts]]
[float] [float]
==== Automatic script reloading ==== Automatic script reloading
@ -181,23 +180,51 @@ Script reloading can be completely disabled by setting
=== Stored Scripts === Stored Scripts
Scripts may be stored in and retrieved from the cluster state using the 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] [source,js]
----------------------------------- -----------------------------------
/_scripts/{lang}/{id} <1> <2> /_scripts/{id} <1>
----------------------------------- -----------------------------------
<1> The `lang` represents the script language. <1> The `id` is a unique identifier for the stored script.
<2> The `id` is a unique identifier or script name.
This example stores a Painless script called `calculate-score` in the cluster This example stores a Painless script called `calculate-score` in the cluster
state: state:
[source,js] [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 // CONSOLE
@ -206,12 +233,12 @@ This same script can be retrieved with:
[source,js] [source,js]
----------------------------------- -----------------------------------
GET _scripts/painless/calculate-score GET _scripts/calculate-score
----------------------------------- -----------------------------------
// CONSOLE // CONSOLE
// TEST[continued] // 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] [source,js]
-------------------------------------------------- --------------------------------------------------
@ -220,7 +247,6 @@ GET _search
"query": { "query": {
"script": { "script": {
"script": { "script": {
"lang": "painless",
"stored": "calculate-score", "stored": "calculate-score",
"params": { "params": {
"my_modifier": 2 "my_modifier": 2
@ -237,7 +263,7 @@ And deleted with:
[source,js] [source,js]
----------------------------------- -----------------------------------
DELETE _scripts/painless/calculate-score DELETE _scripts/calculate-score
----------------------------------- -----------------------------------
// CONSOLE // CONSOLE
// TEST[continued] // TEST[continued]

View File

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

View File

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

View File

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

View File

@ -18,21 +18,32 @@
*/ */
package org.elasticsearch.script.mustache; 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.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest; 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; import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteSearchTemplateAction extends RestDeleteStoredScriptAction { public class RestDeleteSearchTemplateAction extends BaseRestHandler {
public RestDeleteSearchTemplateAction(Settings settings, RestController controller) { public RestDeleteSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false); super(settings);
controller.registerHandler(DELETE, "/_search/template/{id}", this); controller.registerHandler(DELETE, "/_search/template/{id}", this);
} }
@Override @Override
protected String getScriptLang(RestRequest request) { public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
return "mustache"; 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; 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.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.RestController;
import org.elasticsearch.rest.RestRequest; 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; 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) { public RestGetSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false); super(settings);
controller.registerHandler(GET, "/_search/template/{id}", this); controller.registerHandler(GET, "/_search/template/{id}", this);
} }
@Override @Override
protected String getScriptLang(RestRequest request) { public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException {
return "mustache"; String id = request.param("id");
}
@Override GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id, Script.DEFAULT_TEMPLATE_LANG);
protected String getScriptFieldName() {
return TEMPLATE; 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());
}
builder.endObject();
return new BytesRestResponse(found ? RestStatus.OK : RestStatus.NOT_FOUND, builder);
}
});
} }
} }

View File

@ -18,23 +18,36 @@
*/ */
package org.elasticsearch.script.mustache; 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.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest; 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.POST;
import static org.elasticsearch.rest.RestRequest.Method.PUT; import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutSearchTemplateAction extends RestPutStoredScriptAction { public class RestPutSearchTemplateAction extends BaseRestHandler {
public RestPutSearchTemplateAction(Settings settings, RestController controller) { public RestPutSearchTemplateAction(Settings settings, RestController controller) {
super(settings, controller, false); super(settings);
controller.registerHandler(POST, "/_search/template/{id}", this); controller.registerHandler(POST, "/_search/template/{id}", this);
controller.registerHandler(PUT, "/_search/template/{id}", this); controller.registerHandler(PUT, "/_search/template/{id}", this);
} }
@Override @Override
protected String getScriptLang(RestRequest request) { public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
return "mustache"; 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) { public TemplateQueryBuilder(String template, ScriptType scriptType, Map<String, Object> params, XContentType ct) {
this(new Script(scriptType, "mustache", template, this(new Script(scriptType, "mustache", template, scriptType == ScriptType.INLINE ?
ct == null ? Collections.emptyMap() : Collections.singletonMap(Script.CONTENT_TYPE_OPTION, ct.mediaType()), params)); (ct == null ? Collections.emptyMap() : Collections.singletonMap(Script.CONTENT_TYPE_OPTION, ct.mediaType()))
: null, params));
} }
TemplateQueryBuilder(Script template) { TemplateQueryBuilder(Script template) {
@ -138,6 +139,13 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
public static TemplateQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { public static TemplateQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
Script template = Script.parse(parser, Script.DEFAULT_TEMPLATE_LANG); 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); return new TemplateQueryBuilder(template);
} }
} }

View File

@ -150,9 +150,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
public void testIndexedTemplateClient() throws Exception { public void testIndexedTemplateClient() throws Exception {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("testTemplate") .setId("testTemplate")
.setSource(new BytesArray("{" + .setContent(new BytesArray("{" +
"\"template\":{" + "\"template\":{" +
" \"query\":{" + " \"query\":{" +
" \"match\":{" + " \"match\":{" +
@ -163,8 +163,8 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("testTemplate").setSource(new BytesArray("{" + .setId("testTemplate").setContent(new BytesArray("{" +
"\"template\":{" + "\"template\":{" +
" \"query\":{" + " \"query\":{" +
" \"match\":{" + " \"match\":{" +
@ -175,7 +175,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
GetStoredScriptResponse getResponse = client().admin().cluster() GetStoredScriptResponse getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get(); .prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get();
assertNotNull(getResponse.getStoredScript()); assertNotNull(getResponse.getSource());
BulkRequestBuilder bulkRequestBuilder = client().prepareBulk(); BulkRequestBuilder bulkRequestBuilder = client().prepareBulk();
bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}")); bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}"));
@ -200,20 +200,20 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
getResponse = client().admin().cluster() getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get(); .prepareGetStoredScript(MustacheScriptEngineService.NAME, "testTemplate").get();
assertNull(getResponse.getStoredScript()); assertNull(getResponse.getSource());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client()) IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type")) .setRequest(new SearchRequest("test").types("type"))
.setScript("/template_index/mustache/1000").setScriptType(ScriptType.STORED).setScriptParams(templateParams) .setScript("/template_index/mustache/1000").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get()); .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 { public void testIndexedTemplate() throws Exception {
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("1a") .setId("1a")
.setSource(new BytesArray("{" + .setContent(new BytesArray("{" +
"\"template\":{" + "\"template\":{" +
" \"query\":{" + " \"query\":{" +
" \"match\":{" + " \"match\":{" +
@ -224,9 +224,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
)) ))
); );
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("2") .setId("2")
.setSource(new BytesArray("{" + .setContent(new BytesArray("{" +
"\"template\":{" + "\"template\":{" +
" \"query\":{" + " \"query\":{" +
" \"match\":{" + " \"match\":{" +
@ -236,9 +236,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
"}")) "}"))
); );
assertAcked(client().admin().cluster().preparePutStoredScript() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("3") .setId("3")
.setSource(new BytesArray("{" + .setContent(new BytesArray("{" +
"\"template\":{" + "\"template\":{" +
" \"match\":{" + " \"match\":{" +
" \"theField\" : \"{{fieldParam}}\"}" + " \"theField\" : \"{{fieldParam}}\"}" +
@ -260,7 +260,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client()) SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest().indices("test").types("type")) .setRequest(new SearchRequest().indices("test").types("type"))
.setScript("/mustache/1a") .setScript("1a")
.setScriptType(ScriptType.STORED) .setScriptType(ScriptType.STORED)
.setScriptParams(templateParams) .setScriptParams(templateParams)
.get(); .get();
@ -286,6 +286,8 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.setScript("/mustache/2").setScriptType(ScriptType.STORED).setScriptParams(templateParams) .setScript("/mustache/2").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get(); .get();
assertHitCount(searchResponse.getResponse(), 1); 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<>(); Map<String, Object> vars = new HashMap<>();
vars.put("fieldParam", "bar"); vars.put("fieldParam", "bar");
@ -307,45 +309,51 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.get(); .get();
client().admin().indices().prepareRefresh().get(); client().admin().indices().prepareRefresh().get();
assertAcked(client().admin().cluster().preparePutStoredScript() int iterations = randomIntBetween(2, 11);
.setScriptLang(MustacheScriptEngineService.NAME) for (int i = 1; i < iterations; i++) {
.setId("git01") assertAcked(client().admin().cluster().preparePutStoredScript()
.setSource(new BytesArray("{\"template\":{\"query\": {\"match_phrase_prefix\": " + .setLang(MustacheScriptEngineService.NAME)
"{\"searchtext\": {\"query\": \"{{P_Keyword1}}\"," + .setId("git01")
"\"unsupported\": \"unsupported\"}}}}}"))); .setContent(new BytesArray("{\"template\":{\"query\": {\"match\": {\"searchtext\": {\"query\": \"{{P_Keyword1}}\"," +
"\"type\": \"ooophrase_prefix\"}}}}}")));
GetStoredScriptResponse getResponse = client().admin().cluster() GetStoredScriptResponse getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngineService.NAME, "git01").get(); .prepareGetStoredScript(MustacheScriptEngineService.NAME, "git01").get();
assertNotNull(getResponse.getStoredScript()); assertNotNull(getResponse.getSource());
Map<String, Object> templateParams = new HashMap<>(); Map<String, Object> templateParams = new HashMap<>();
templateParams.put("P_Keyword1", "dev"); templateParams.put("P_Keyword1", "dev");
ParsingException e = expectThrows(ParsingException.class, () -> new SearchTemplateRequestBuilder(client()) ParsingException e = expectThrows(ParsingException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("testindex").types("test")) .setRequest(new SearchRequest("testindex").types("test"))
.setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams) .setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get()); .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() assertAcked(client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("git01") .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()) SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("testindex").types("test")) .setRequest(new SearchRequest("testindex").types("test"))
.setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams) .setScript("git01").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get(); .get();
assertHitCount(searchResponse.getResponse(), 1); assertHitCount(searchResponse.getResponse(), 1);
assertWarnings("Deprecated field [type] used, replaced by [match_phrase and match_phrase_prefix query]");
}
} }
public void testIndexedTemplateWithArray() throws Exception { public void testIndexedTemplateWithArray() throws Exception {
String multiQuery = "{\"query\":{\"terms\":{\"theField\":[\"{{#fieldParam}}\",\"{{.}}\",\"{{/fieldParam}}\"]}}}"; String multiQuery = "{\"query\":{\"terms\":{\"theField\":[\"{{#fieldParam}}\",\"{{.}}\",\"{{/fieldParam}}\"]}}}";
assertAcked( assertAcked(
client().admin().cluster().preparePutStoredScript() client().admin().cluster().preparePutStoredScript()
.setScriptLang(MustacheScriptEngineService.NAME) .setLang(MustacheScriptEngineService.NAME)
.setId("4") .setId("4")
.setSource(jsonBuilder().startObject().field("template", multiQuery).endObject().bytes()) .setContent(jsonBuilder().startObject().field("template", multiQuery).endObject().bytes())
); );
BulkRequestBuilder bulkRequestBuilder = client().prepareBulk(); BulkRequestBuilder bulkRequestBuilder = client().prepareBulk();
bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}")); bulkRequestBuilder.add(client().prepareIndex("test", "type", "1").setSource("{\"theField\":\"foo\"}"));
@ -362,7 +370,7 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client()) SearchTemplateResponse searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type")) .setRequest(new SearchRequest("test").types("type"))
.setScript("/mustache/4").setScriptType(ScriptType.STORED).setScriptParams(arrayTemplateParams) .setScript("4").setScriptType(ScriptType.STORED).setScriptParams(arrayTemplateParams)
.get(); .get();
assertHitCount(searchResponse.getResponse(), 5); assertHitCount(searchResponse.getResponse(), 5);
} }

View File

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

View File

@ -150,7 +150,7 @@
- match: { hits.total: 1 } - match: { hits.total: 1 }
- do: - 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: search_template:
body: { "file" : "simple1"} body: { "file" : "simple1"}

View File

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

View File

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

View File

@ -70,3 +70,97 @@
ingest.get_pipeline: ingest.get_pipeline:
id: "my_pipeline" id: "my_pipeline"
- match: { my_pipeline.description: "_description" } - 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 } - 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: ingest.get_pipeline:
id: "my_pipeline" id: "my_pipeline"
- match: { my_pipeline.description: "_description" } - 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": "Test script processor with stored script":
- skip:
features: warnings
- do: - do:
put_script: put_script:
id: "sum_bytes" lang: "sum_bytes"
lang: "painless"
body: > 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 } - match: { acknowledged: true }

View File

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

View File

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

View File

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