org.elasticsearch.script.Script Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContentObject;
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 java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* {@link Script} represents used-defined input that can be used to
* compile and execute a script from the {@link ScriptService}
* based on the {@link ScriptType}.
*
* There are three types of scripts specified by {@link ScriptType}.
*
* The following describes the expected parameters for each type of script:
*
*
* - {@link ScriptType#INLINE}
*
* - {@link Script#lang} - specifies the language, defaults to {@link Script#DEFAULT_SCRIPT_LANG}
*
- {@link Script#idOrCode} - specifies the code to be compiled, must not be {@code null}
*
- {@link Script#options} - specifies the compiler options for this script; must not be {@code null},
* use an empty {@link Map} to specify no options
*
- {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
*
* - {@link ScriptType#STORED}
*
* - {@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
*
- {@link Script#idOrCode} - specifies the id of the stored script to be looked up, must not be {@code null}
*
- {@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}
*
- {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
*
* - {@link ScriptType#FILE}
*
* - {@link Script#lang} - specifies the language for look up, defaults to {@link Script#DEFAULT_SCRIPT_LANG}
*
- {@link Script#idOrCode} - specifies the id of the file script to be looked up, must not be {@code null}
*
- {@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}
*
- {@link Script#params} - {@link Map} of user-defined parameters; must not be {@code null},
* use an empty {@link Map} to specify no params
*
*
*/
public final class Script implements ToXContentObject, Writeable {
public static final Version V_5_1_0_UNRELEASED = Version.fromId(5010099);
/**
* 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.
*/
public static final String DEFAULT_SCRIPT_LANG = "painless";
/**
* The name of the default template language.
*/
public static final String DEFAULT_TEMPLATE_LANG = "mustache";
/**
* The default {@link ScriptType}.
*/
public static final ScriptType DEFAULT_SCRIPT_TYPE = ScriptType.INLINE;
/**
* Compiler option for {@link XContentType} used for templates.
*/
public static final String CONTENT_TYPE_OPTION = "content_type";
/**
* 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");
/**
* Helper class used by {@link ObjectParser} to store mutable {@link Script} variables and then
* construct an immutable {@link Script} object based on parsed XContent.
*/
private static final class Builder {
private ScriptType type;
private String lang;
private String idOrCode;
private Map options;
private Map 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();
}
/**
* 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) {
//this is really for search templates, that need to be converted to json format
XContentBuilder builder = XContentFactory.jsonBuilder();
idOrCode = builder.copyCurrentStructure(parser).string();
options.put(CONTENT_TYPE_OPTION, XContentType.JSON.mediaType());
} else {
idOrCode = parser.text();
}
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}
/**
* 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;
}
/**
* 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;
}
/**
* 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 options) {
this.options.putAll(options);
}
private void setParams(Map 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 [source] for an inline script, [id] for a stored script, " +
"or [" + ScriptType.FILE.getParseField().getPreferredName() + "] for a file script");
}
if (type == ScriptType.INLINE) {
if (lang == null) {
lang = defaultLang;
}
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify 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 ");
}
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify 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 for an [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
}
if (options.isEmpty()) {
options = null;
} else {
throw new IllegalArgumentException("field [" + OPTIONS_PARSE_FIELD.getPreferredName() + "] " +
"cannot be specified using a [" + ScriptType.FILE.getParseField().getPreferredName() + "] script");
}
}
return new Script(type, lang, idOrCode, options, params);
}
}
private static final ObjectParser 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);
}
/**
* Convenience method to call {@link Script#parse(XContentParser, String)}
* using the default scripting language.
*/
public static Script parse(XContentParser parser) throws IOException {
return parse(parser, DEFAULT_SCRIPT_LANG);
}
/**
* 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
* {
* // Exactly one of "id" or "source" must be specified
* "id" : "",
* // OR
* "source": "