summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/script/Script.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/org/elasticsearch/script/Script.java')
-rw-r--r--core/src/main/java/org/elasticsearch/script/Script.java701
1 files changed, 510 insertions, 191 deletions
diff --git a/core/src/main/java/org/elasticsearch/script/Script.java b/core/src/main/java/org/elasticsearch/script/Script.java
index edcf666681..59ac761017 100644
--- a/core/src/main/java/org/elasticsearch/script/Script.java
+++ b/core/src/main/java/org/elasticsearch/script/Script.java
@@ -19,281 +19,600 @@
package org.elasticsearch.script;
-import org.elasticsearch.ElasticsearchParseException;
-import org.elasticsearch.common.Nullable;
+import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
-import org.elasticsearch.common.ParsingException;
+import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.common.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.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
- * Script holds all the parameters necessary to compile or find in cache and then execute a script.
+ * Script represents used-defined input that can be used to
+ * compile and execute a script from the {@link ScriptService}
+ * based on the {@link ScriptType}.
*/
public final class Script implements ToXContent, Writeable {
+ /**
+ * The name of the of the default scripting language.
+ */
public static final String DEFAULT_SCRIPT_LANG = "painless";
- private String script;
- private ScriptType type;
- @Nullable private String lang;
- @Nullable private Map<String, Object> params;
- @Nullable private XContentType contentType;
+ /**
+ * The name of the default template language.
+ */
+ public static final String DEFAULT_TEMPLATE_LANG = "mustache";
/**
- * Constructor for simple inline script. The script will have no lang or params set.
- *
- * @param script The inline script to execute.
+ * The default {@link ScriptType}.
*/
- public Script(String script) {
- this(script, ScriptType.INLINE, null, null);
- }
+ public static final ScriptType DEFAULT_SCRIPT_TYPE = ScriptType.INLINE;
- public Script(String script, ScriptType type, String lang, @Nullable Map<String, ?> params) {
- this(script, type, lang, params, null);
- }
+ /**
+ * Compiler option for {@link XContentType} used for templates.
+ */
+ public static final String CONTENT_TYPE_OPTION = "content_type";
/**
- * Constructor for Script.
- *
- * @param script The cache key of the script to be compiled/executed. For inline scripts this is the actual
- * script source code. For indexed scripts this is the id used in the request. For on file
- * scripts this is the file name.
- * @param type The type of script -- dynamic, stored, or file.
- * @param lang The language of the script to be compiled/executed.
- * @param params The map of parameters the script will be executed with.
- * @param contentType The {@link XContentType} of the script. Only relevant for inline scripts that have not been
- * defined as a plain string, but as json or yaml content. This class needs this information
- * when serializing the script back to xcontent.
+ * Standard {@link ParseField} for outer level of script queries.
+ */
+ public static final ParseField SCRIPT_PARSE_FIELD = new ParseField("script");
+
+ /**
+ * Standard {@link ParseField} for lang on the inner level.
+ */
+ public static final ParseField LANG_PARSE_FIELD = new ParseField("lang");
+
+ /**
+ * Standard {@link ParseField} for options on the inner level.
+ */
+ public static final ParseField OPTIONS_PARSE_FIELD = new ParseField("options");
+
+ /**
+ * Standard {@link ParseField} for params on the inner level.
+ */
+ public static final ParseField PARAMS_PARSE_FIELD = new ParseField("params");
+
+ /**
+ * Unreleased version used for {@link Script} non-null members format of read/write.
+ */
+ public static final Version V_5_1_0_UNRELEASED = Version.fromId(5010099);
+
+ /**
+ * Helper class used by {@link ObjectParser} to store mutable {@link Script} variables and then
+ * construct an immutable {@link Script} object based on parsed XContent.
*/
- @SuppressWarnings("unchecked")
- public Script(String script, ScriptType type, String lang, @Nullable Map<String, ?> params,
- @Nullable XContentType contentType) {
- if (contentType != null && type != ScriptType.INLINE) {
- throw new IllegalArgumentException("The parameter contentType only makes sense for inline scripts");
+ private static final class Builder {
+ private ScriptType type;
+ private String lang;
+ private String idOrCode;
+ private Map<String, String> options;
+ private Map<String, Object> params;
+
+ private Builder() {
+ // This cannot default to an empty map because options are potentially added at multiple points.
+ this.options = new HashMap<>();
+ this.params = Collections.emptyMap();
}
- this.script = Objects.requireNonNull(script);
- this.type = Objects.requireNonNull(type);
- this.lang = lang == null ? DEFAULT_SCRIPT_LANG : lang;
- this.params = (Map<String, Object>) params;
- this.contentType = contentType;
- }
- public Script(StreamInput in) throws IOException {
- script = in.readString();
- if (in.readBoolean()) {
- type = ScriptType.readFrom(in);
+ /**
+ * Since inline scripts can accept code rather than just an id, 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 setInline(XContentParser parser) {
+ try {
+ if (type != null) {
+ throwOnlyOneOfType();
+ }
+
+ type = ScriptType.INLINE;
+
+ if (parser.currentToken() == Token.START_OBJECT) {
+ XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType());
+ idOrCode = builder.copyCurrentStructure(parser).bytes().utf8ToString();
+ options.put(CONTENT_TYPE_OPTION, parser.contentType().mediaType());
+ } else {
+ idOrCode = parser.text();
+ }
+ } catch (IOException exception) {
+ throw new UncheckedIOException(exception);
+ }
}
- lang = in.readOptionalString();
- params = in.readMap();
- if (in.readBoolean()) {
- contentType = XContentType.readFrom(in);
+
+ /**
+ * Set both the id and the type of the stored script.
+ */
+ private void setStored(String idOrCode) {
+ if (type != null) {
+ throwOnlyOneOfType();
+ }
+
+ type = ScriptType.STORED;
+ this.idOrCode = idOrCode;
}
- }
- @Override
- public void writeTo(StreamOutput out) throws IOException {
- out.writeString(script);
- boolean hasType = type != null;
- out.writeBoolean(hasType);
- if (hasType) {
- type.writeTo(out);
+ /**
+ * Set both the id and the type of the file script.
+ */
+ private void setFile(String idOrCode) {
+ if (type != null) {
+ throwOnlyOneOfType();
+ }
+
+ type = ScriptType.FILE;
+ this.idOrCode = idOrCode;
}
- out.writeOptionalString(lang);
- out.writeMap(params);
- boolean hasContentType = contentType != null;
- out.writeBoolean(hasContentType);
- if (hasContentType) {
- XContentType.writeTo(contentType, out);
+
+ /**
+ * Helper method to throw an exception if more than one type of {@link Script} is specified.
+ */
+ private void throwOnlyOneOfType() {
+ throw new IllegalArgumentException("must only use one of [" +
+ ScriptType.INLINE.getParseField().getPreferredName() + " + , " +
+ ScriptType.STORED.getParseField().getPreferredName() + " + , " +
+ ScriptType.FILE.getParseField().getPreferredName() + "]" +
+ " when specifying a script");
+ }
+
+ private void setLang(String lang) {
+ this.lang = lang;
+ }
+
+ /**
+ * Options may have already been added if an inline template was specified.
+ * Appends the user-defined compiler options with the internal compiler options.
+ */
+ private void setOptions(Map<String, String> options) {
+ this.options.putAll(options);
+ }
+
+ private void setParams(Map<String, Object> params) {
+ this.params = params;
+ }
+
+ /**
+ * Validates the parameters and creates an {@link Script}.
+ * @param defaultLang The default lang is not a compile-time constant and must be provided
+ * at run-time this way in case a legacy default language is used from
+ * previously stored queries.
+ */
+ private Script build(String defaultLang) {
+ if (type == null) {
+ throw new IllegalArgumentException(
+ "must specify either code for an [" + ScriptType.INLINE.getParseField().getPreferredName() + "] script " +
+ "or an id for a [" + ScriptType.STORED.getParseField().getPreferredName() + "] script " +
+ "or [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
+ }
+
+ if (idOrCode == null) {
+ throw new IllegalArgumentException("must specify an id or code for a script");
+ }
+
+ if (options.size() > 1 || options.size() == 1 && options.get(CONTENT_TYPE_OPTION) == null) {
+ throw new IllegalArgumentException("illegal compiler options [" + options + "] specified");
+ }
+
+ return new Script(type, this.lang == null ? defaultLang : this.lang, idOrCode, options, params);
}
}
+ private static final ObjectParser<Builder, ParseFieldMatcherSupplier> PARSER = new ObjectParser<>("script", Builder::new);
+
+ static {
+ // Defines the fields necessary to parse a Script as XContent using an ObjectParser.
+ PARSER.declareField(Builder::setInline, parser -> parser, ScriptType.INLINE.getParseField(), ValueType.OBJECT_OR_STRING);
+ PARSER.declareString(Builder::setStored, ScriptType.STORED.getParseField());
+ PARSER.declareString(Builder::setFile, ScriptType.FILE.getParseField());
+ PARSER.declareString(Builder::setLang, LANG_PARSE_FIELD);
+ PARSER.declareField(Builder::setOptions, XContentParser::mapStrings, OPTIONS_PARSE_FIELD, ValueType.OBJECT);
+ PARSER.declareField(Builder::setParams, XContentParser::map, PARAMS_PARSE_FIELD, ValueType.OBJECT);
+ }
+
/**
- * Method for getting the script.
- * @return The cache key of the script to be compiled/executed. For dynamic scripts this is the actual
- * script source code. For indexed scripts this is the id used in the request. For on disk scripts
- * this is the file name.
+ * Convenience method to call {@link Script#parse(XContentParser, ParseFieldMatcher, String)}
+ * using the default scripting language.
*/
- public String getScript() {
- return script;
+ public static Script parse(XContentParser parser, ParseFieldMatcher matcher) throws IOException {
+ return parse(parser, matcher, DEFAULT_SCRIPT_LANG);
}
/**
- * Method for getting the type.
- *
- * @return The type of script -- inline, stored, or file.
+ * Convenience method to call {@link Script#parse(XContentParser, ParseFieldMatcher, String)} using the
+ * {@link ParseFieldMatcher} and scripting language provided by the {@link QueryParseContext}.
*/
- public ScriptType getType() {
- return type;
+ public static Script parse(XContentParser parser, QueryParseContext context) throws IOException {
+ return parse(parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
}
/**
- * Method for getting language.
+ * This will parse XContent into a {@link Script}. The following formats can be parsed:
+ *
+ * The simple format defaults to an {@link ScriptType#INLINE} with no compiler options or user-defined params:
+ *
+ * Example:
+ * {@code
+ * "return Math.log(doc.popularity) * 100;"
+ * }
+ *
+ * The complex format where {@link ScriptType} and idOrCode are required while lang, options and params are not required.
+ *
+ * {@code
+ * {
+ * "<type (inline, stored, file)>" : "<idOrCode>",
+ * "lang" : "<lang>",
+ * "options" : {
+ * "option0" : "<option0>",
+ * "option1" : "<option1>",
+ * ...
+ * },
+ * "params" : {
+ * "param0" : "<param0>",
+ * "param1" : "<param1>",
+ * ...
+ * }
+ * }
+ * }
+ *
+ * Example:
+ * {@code
+ * {
+ * "inline" : "return Math.log(doc.popularity) * params.multiplier",
+ * "lang" : "painless",
+ * "params" : {
+ * "multiplier" : 100.0
+ * }
+ * }
+ * }
+ *
+ * This also handles templates in a special way. If a complexly formatted query is specified as another complex
+ * JSON object the query is assumed to be a template, and the format will be preserved.
*
- * @return The language of the script to be compiled/executed.
+ * {@code
+ * {
+ * "inline" : { "query" : ... },
+ * "lang" : "<lang>",
+ * "options" : {
+ * "option0" : "<option0>",
+ * "option1" : "<option1>",
+ * ...
+ * },
+ * "params" : {
+ * "param0" : "<param0>",
+ * "param1" : "<param1>",
+ * ...
+ * }
+ * }
+ * }
+ *
+ * @param parser The {@link XContentParser} to be used.
+ * @param matcher The {@link ParseFieldMatcher} to be used.
+ * @param defaultLang The default language to use if no language is specified. The default language isn't necessarily
+ * the one defined by {@link Script#DEFAULT_SCRIPT_LANG} due to backwards compatiblity requirements
+ * related to stored queries using previously default languauges.
+ * @return The parsed {@link Script}.
*/
- public String getLang() {
- return lang;
+ public static Script parse(XContentParser parser, ParseFieldMatcher matcher, String defaultLang) throws IOException {
+ Objects.requireNonNull(defaultLang);
+
+ Token token = parser.currentToken();
+
+ if (token == null) {
+ token = parser.nextToken();
+ }
+
+ if (token == Token.VALUE_STRING) {
+ return new Script(ScriptType.INLINE, defaultLang, parser.text(), Collections.emptyMap());
+ }
+
+ return PARSER.apply(parser, () -> matcher).build(defaultLang);
}
+ private final ScriptType type;
+ private final String lang;
+ private final String idOrCode;
+ private final Map<String, String> options;
+ private final Map<String, Object> params;
+
/**
- * Method for getting the parameters.
- *
- * @return The map of parameters the script will be executed with.
+ * Constructor for simple script using the default language and default type.
+ * @param idOrCode The id or code to use dependent on the default script type.
*/
- public Map<String, Object> getParams() {
- return params;
+ public Script(String idOrCode) {
+ this(DEFAULT_SCRIPT_TYPE, DEFAULT_SCRIPT_LANG, idOrCode, Collections.emptyMap(), Collections.emptyMap());
}
/**
- * @return The content type of the script if it is an inline script and the script has been defined as json
- * or yaml content instead of a plain string.
+ * Constructor for a script that does not need to use compiler options.
+ * @param type The {@link ScriptType}.
+ * @param lang The lang for this {@link Script}.
+ * @param idOrCode The id for this {@link Script} if the {@link ScriptType} is {@link ScriptType#FILE} or {@link ScriptType#STORED}.
+ * The code for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE}.
+ * @param params The user-defined params to be bound for script execution.
*/
- public XContentType getContentType() {
- return contentType;
+ public Script(ScriptType type, String lang, String idOrCode, Map<String, Object> params) {
+ this(type, lang, idOrCode, Collections.emptyMap(), params);
+ }
+
+ /**
+ * Constructor for a script that requires the use of compiler options.
+ * @param type The {@link ScriptType}.
+ * @param lang The lang for this {@link Script}.
+ * @param idOrCode The id for this {@link Script} if the {@link ScriptType} is {@link ScriptType#FILE} or {@link ScriptType#STORED}.
+ * The code for this {@link Script} if the {@link ScriptType} is {@link ScriptType#INLINE}.
+ * @param options The options to be passed to the compiler for use at compile-time.
+ * @param params The user-defined params to be bound for script execution.
+ */
+ public Script(ScriptType type, String lang, String idOrCode, Map<String, String> options, Map<String, Object> params) {
+ this.idOrCode = Objects.requireNonNull(idOrCode);
+ this.type = Objects.requireNonNull(type);
+ this.lang = Objects.requireNonNull(lang);
+ this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
+ this.params = Collections.unmodifiableMap(Objects.requireNonNull(params));
+
+ if (type != ScriptType.INLINE && !options.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Compiler options [" + options + "] cannot be specified at runtime for [" + type + "] scripts.");
+ }
+ }
+
+ /**
+ * Creates a {@link Script} read from an input stream.
+ */
+ public Script(StreamInput in) throws IOException {
+ // Version 5.1+ requires all Script members to be non-null and supports the potential
+ // for more options than just XContentType. Reorders the read in contents to be in
+ // same order as the constructor.
+ if (in.getVersion().onOrAfter(V_5_1_0_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();
+ this.options = options;
+ this.params = in.readMap();
+ // Prior to version 5.1 the script members are read in certain cases as optional and given
+ // default values when necessary. Also the only option supported is for XContentType.
+ } else {
+ String idOrCode = in.readString();
+ ScriptType type;
+
+ if (in.readBoolean()) {
+ type = ScriptType.readFrom(in);
+ } else {
+ type = DEFAULT_SCRIPT_TYPE;
+ }
+
+ String lang = in.readOptionalString();
+
+ if (lang == null) {
+ lang = DEFAULT_SCRIPT_LANG;
+ }
+
+ Map<String, Object> params = in.readMap();
+
+ if (params == null) {
+ params = new HashMap<>();
+ }
+
+ Map<String, String> options = new HashMap<>();
+
+ if (in.readBoolean()) {
+ XContentType contentType = XContentType.readFrom(in);
+ options.put(CONTENT_TYPE_OPTION, contentType.mediaType());
+ }
+
+ this.type = type;
+ this.lang = lang;
+ this.idOrCode = idOrCode;
+ this.options = options;
+ this.params = params;
+ }
}
@Override
- public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException {
- if (type == null) {
- return builder.value(script);
+ public void writeTo(StreamOutput out) throws IOException {
+ // Version 5.1+ requires all Script members to be non-null and supports the potential
+ // for more options than just XContentType. Reorders the written out contents to be in
+ // same order as the constructor.
+ if (out.getVersion().onOrAfter(V_5_1_0_UNRELEASED)) {
+ type.writeTo(out);
+ out.writeString(lang);
+ out.writeString(idOrCode);
+ @SuppressWarnings("unchecked")
+ Map<String, Object> options = (Map<String, Object>)(Map)this.options;
+ out.writeMap(options);
+ out.writeMap(params);
+ // Prior to version 5.1 the Script members were possibly written as optional or null, though this is no longer
+ // necessary since Script members cannot be null anymore, and there is no case where a null value wasn't equivalent
+ // to it's default value when actually compiling/executing a script. Meaning, there are no backwards compatibility issues,
+ // and now there's enforced consistency. Also the only supported compiler option was XContentType.
+ } else {
+ out.writeString(idOrCode);
+ out.writeBoolean(true);
+ type.writeTo(out);
+ out.writeBoolean(true);
+ out.writeString(lang);
+ out.writeMap(params.isEmpty() ? null : params);
+
+ if (options.containsKey(CONTENT_TYPE_OPTION)) {
+ XContentType contentType = XContentType.fromMediaTypeOrFormat(options.get(CONTENT_TYPE_OPTION));
+ out.writeBoolean(true);
+ contentType.writeTo(out);
+ } else {
+ out.writeBoolean(false);
+ }
}
+ }
+
+ /**
+ * This will build scripts into the following XContent structure:
+ *
+ * {@code
+ * {
+ * "<type (inline, stored, file)>" : "<idOrCode>",
+ * "lang" : "<lang>",
+ * "options" : {
+ * "option0" : "<option0>",
+ * "option1" : "<option1>",
+ * ...
+ * },
+ * "params" : {
+ * "param0" : "<param0>",
+ * "param1" : "<param1>",
+ * ...
+ * }
+ * }
+ * }
+ *
+ * Example:
+ * {@code
+ * {
+ * "inline" : "return Math.log(doc.popularity) * params.multiplier;",
+ * "lang" : "painless",
+ * "params" : {
+ * "multiplier" : 100.0
+ * }
+ * }
+ * }
+ *
+ * Note that options and params will only be included if there have been any specified.
+ *
+ * This also handles templates in a special way. If the {@link Script#CONTENT_TYPE_OPTION} option
+ * is provided and the {@link ScriptType#INLINE} is specified then the template will be preserved as a raw field.
+ *
+ * {@code
+ * {
+ * "inline" : { "query" : ... },
+ * "lang" : "<lang>",
+ * "options" : {
+ * "option0" : "<option0>",
+ * "option1" : "<option1>",
+ * ...
+ * },
+ * "params" : {
+ * "param0" : "<param0>",
+ * "param1" : "<param1>",
+ * ...
+ * }
+ * }
+ * }
+ */
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params builderParams) throws IOException {
builder.startObject();
- if (type == ScriptType.INLINE && contentType != null && builder.contentType() == contentType) {
- builder.rawField(type.getParseField().getPreferredName(), new BytesArray(script));
+
+ String contentType = options.get(CONTENT_TYPE_OPTION);
+
+ if (type == ScriptType.INLINE && contentType != null && builder.contentType().mediaType().equals(contentType)) {
+ builder.rawField(type.getParseField().getPreferredName(), new BytesArray(idOrCode));
} else {
- builder.field(type.getParseField().getPreferredName(), script);
+ builder.field(type.getParseField().getPreferredName(), idOrCode);
}
- if (lang != null) {
- builder.field(ScriptField.LANG.getPreferredName(), lang);
+
+ builder.field(LANG_PARSE_FIELD.getPreferredName(), lang);
+
+ if (!options.isEmpty()) {
+ builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options);
}
- if (params != null) {
- builder.field(ScriptField.PARAMS.getPreferredName(), params);
+
+ if (!params.isEmpty()) {
+ builder.field(PARAMS_PARSE_FIELD.getPreferredName(), params);
}
+
builder.endObject();
+
return builder;
}
- public static Script parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException {
- return parse(parser, parseFieldMatcher, null);
+ /**
+ * @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}.
+ */
+ public String getIdOrCode() {
+ return idOrCode;
+ }
+
+ /**
+ * @return The {@link ScriptType} for this {@link Script}.
+ */
+ public ScriptType getType() {
+ return type;
}
- public static Script parse(XContentParser parser, QueryParseContext context) {
- try {
- return parse(parser, context.getParseFieldMatcher(), context.getDefaultScriptLanguage());
- } catch (IOException e) {
- throw new ParsingException(parser.getTokenLocation(), "Error parsing [" + ScriptField.SCRIPT.getPreferredName() + "] field", e);
- }
+ /**
+ * @return The language for this {@link Script}.
+ */
+ public String getLang() {
+ return lang;
}
- public static Script parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher, @Nullable String lang) throws IOException {
- XContentParser.Token token = parser.currentToken();
- // If the parser hasn't yet been pushed to the first token, do it now
- if (token == null) {
- token = parser.nextToken();
- }
- if (token == XContentParser.Token.VALUE_STRING) {
- return new Script(parser.text(), ScriptType.INLINE, lang, null);
- }
- if (token != XContentParser.Token.START_OBJECT) {
- throw new ElasticsearchParseException("expected a string value or an object, but found [{}] instead", token);
- }
- String script = null;
- ScriptType type = null;
- Map<String, Object> params = null;
- XContentType contentType = null;
- String cfn = null;
- while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
- if (token == XContentParser.Token.FIELD_NAME) {
- cfn = parser.currentName();
- } else if (parseFieldMatcher.match(cfn, ScriptType.INLINE.getParseField())) {
- type = ScriptType.INLINE;
- if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
- contentType = parser.contentType();
- XContentBuilder builder = XContentFactory.contentBuilder(contentType);
- script = builder.copyCurrentStructure(parser).bytes().utf8ToString();
- } else {
- script = parser.text();
- }
- } else if (parseFieldMatcher.match(cfn, ScriptType.FILE.getParseField())) {
- type = ScriptType.FILE;
- if (token == XContentParser.Token.VALUE_STRING) {
- script = parser.text();
- } else {
- throw new ElasticsearchParseException("expected a string value for field [{}], but found [{}]", cfn, token);
- }
- } else if (parseFieldMatcher.match(cfn, ScriptType.STORED.getParseField())) {
- type = ScriptType.STORED;
- if (token == XContentParser.Token.VALUE_STRING) {
- script = parser.text();
- } else {
- throw new ElasticsearchParseException("expected a string value for field [{}], but found [{}]", cfn, token);
- }
- } else if (parseFieldMatcher.match(cfn, ScriptField.LANG)) {
- if (token == XContentParser.Token.VALUE_STRING) {
- lang = parser.text();
- } else {
- throw new ElasticsearchParseException("expected a string value for field [{}], but found [{}]", cfn, token);
- }
- } else if (parseFieldMatcher.match(cfn, ScriptField.PARAMS)) {
- if (token == XContentParser.Token.START_OBJECT) {
- params = parser.map();
- } else {
- throw new ElasticsearchParseException("expected an object for field [{}], but found [{}]", cfn, token);
- }
- } else {
- throw new ElasticsearchParseException("unexpected field [{}]", cfn);
- }
- }
- if (script == null) {
- throw new ElasticsearchParseException("expected one of [{}], [{}] or [{}] fields, but found none",
- ScriptType.INLINE.getParseField() .getPreferredName(), ScriptType.FILE.getParseField().getPreferredName(),
- ScriptType.STORED.getParseField() .getPreferredName());
- }
- return new Script(script, type, lang, params, contentType);
+ /**
+ * @return The map of compiler options for this {@link Script}.
+ */
+ public Map<String, String> getOptions() {
+ return options;
}
- @Override
- public int hashCode() {
- return Objects.hash(lang, params, script, type, contentType);
+ /**
+ * @return The map of user-defined params for this {@link Script}.
+ */
+ public Map<String, Object> getParams() {
+ return params;
}
@Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- Script other = (Script) obj;
-
- return Objects.equals(lang, other.lang) &&
- Objects.equals(params, other.params) &&
- Objects.equals(script, other.script) &&
- Objects.equals(type, other.type) &&
- Objects.equals(contentType, other.contentType);
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Script script = (Script)o;
+
+ if (type != script.type) return false;
+ if (!lang.equals(script.lang)) return false;
+ if (!idOrCode.equals(script.idOrCode)) return false;
+ if (!options.equals(script.options)) return false;
+ return params.equals(script.params);
+
}
@Override
- public String toString() {
- return "[script: " + script + ", type: " + type.getParseField().getPreferredName() + ", lang: "
- + lang + ", params: " + params + "]";
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + lang.hashCode();
+ result = 31 * result + idOrCode.hashCode();
+ result = 31 * result + options.hashCode();
+ result = 31 * result + params.hashCode();
+ return result;
}
- public interface ScriptField {
- ParseField SCRIPT = new ParseField("script");
- ParseField LANG = new ParseField("lang");
- ParseField PARAMS = new ParseField("params");
+ @Override
+ public String toString() {
+ return "Script{" +
+ "type=" + type +
+ ", lang='" + lang + '\'' +
+ ", idOrCode='" + idOrCode + '\'' +
+ ", options=" + options +
+ ", params=" + params +
+ '}';
}
-
}