Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.script;
import org.opensearch.cluster.AbstractDiffable;
import org.opensearch.cluster.ClusterState;
import org.opensearch.cluster.Diff;
import org.opensearch.common.ParseField;
import org.opensearch.common.ParsingException;
import org.opensearch.common.Strings;
import org.opensearch.common.bytes.BytesReference;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.io.stream.Writeable;
import org.opensearch.common.logging.DeprecationLogger;
import org.opensearch.common.xcontent.LoggingDeprecationHandler;
import org.opensearch.common.xcontent.NamedXContentRegistry;
import org.opensearch.common.xcontent.ObjectParser;
import org.opensearch.common.xcontent.ObjectParser.ValueType;
import org.opensearch.common.xcontent.ToXContentObject;
import org.opensearch.common.xcontent.XContentBuilder;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.common.xcontent.XContentParser.Token;
import org.opensearch.common.xcontent.XContentType;
import java.io.IOException;
import java.io.InputStream;
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 implements Writeable, ToXContentObject {
/**
* Standard deprecation logger for used to deprecate allowance of empty templates.
*/
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(StoredScriptSource.class);
/**
* Standard {@link ParseField} for outer level of stored script source.
*/
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 source on the inner level.
*/
public static final ParseField SOURCE_PARSE_FIELD = new ParseField("source", "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 source;
private Map 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 source. 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 setSource(XContentParser parser) {
try {
if (parser.currentToken() == Token.START_OBJECT) {
// this is really for search templates, that need to be converted to json format
XContentBuilder builder = XContentFactory.jsonBuilder();
source = Strings.toString(builder.copyCurrentStructure(parser));
options.put(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType());
} else {
source = 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 options) {
this.options.putAll(options);
}
/**
* Validates the parameters and creates an {@link StoredScriptSource}.
*
* @param ignoreEmpty Specify as {@code true} to ignoreEmpty the empty source check.
* This allow empty templates to be loaded for backwards compatibility.
* This allow empty templates to be loaded for backwards compatibility.
*/
private StoredScriptSource build(boolean ignoreEmpty) {
if (lang == null) {
throw new IllegalArgumentException("must specify lang for stored script");
} else if (lang.isEmpty()) {
throw new IllegalArgumentException("lang cannot be empty");
}
if (source == null) {
if (ignoreEmpty || Script.DEFAULT_TEMPLATE_LANG.equals(lang)) {
if (Script.DEFAULT_TEMPLATE_LANG.equals(lang)) {
deprecationLogger.deprecate("empty_templates", "empty templates should no longer be used");
} else {
deprecationLogger.deprecate("empty_scripts", "empty scripts should no longer be used");
}
} else {
throw new IllegalArgumentException("must specify source for stored script");
}
} else if (source.isEmpty()) {
if (ignoreEmpty || Script.DEFAULT_TEMPLATE_LANG.equals(lang)) {
if (Script.DEFAULT_TEMPLATE_LANG.equals(lang)) {
deprecationLogger.deprecate("empty_templates", "empty templates should no longer be used");
} else {
deprecationLogger.deprecate("empty_scripts", "empty scripts should no longer be used");
}
} else {
throw new IllegalArgumentException("source 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, source, options);
}
}
private static final ObjectParser PARSER = new ObjectParser<>("stored script source", true, 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::setSource, parser -> parser, SOURCE_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 source are required but options is optional:
*
* {@code
* {
* "script" : {
* "lang" : "",
* "source" : "",
* "options" : {
* "option0" : "",
* "option1" : "",
* ...
* }
* }
* }
* }
*
* Example:
* {@code
* {
* "script": {
* "lang" : "painless",
* "source" : "return Math.log(doc.popularity) * params.multiplier"
* }
* }
* }
*
* The use of "source" may also be substituted with "code" for backcompat with 5.3 to 5.5 format. For example:
*
* {@code
* {
* "script" : {
* "lang" : "",
* "code" : "",
* "options" : {
* "option0" : "",
* "option1" : "",
* ...
* }
* }
* }
* }
*
* Note that the "source" parameter can also handle template parsing including from
* a complex JSON object.
*
* @param content The content from the request to be parsed as described above.
* @return The parsed {@link StoredScriptSource}.
*/
public static StoredScriptSource parse(BytesReference content, XContentType xContentType) {
try (
InputStream stream = content.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)
) {
Token token = parser.nextToken();
if (token != Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{]");
}
token = parser.nextToken();
if (token == Token.END_OBJECT) {
deprecationLogger.deprecate("empty_templates", "empty templates should no longer be used");
return new StoredScriptSource(Script.DEFAULT_TEMPLATE_LANG, "", Collections.emptyMap());
}
if (token != Token.FIELD_NAME) {
throw new ParsingException(
parser.getTokenLocation(),
"unexpected token [" + token + ", expected [" + SCRIPT_PARSE_FIELD.getPreferredName() + "]"
);
}
String name = parser.currentName();
if (SCRIPT_PARSE_FIELD.getPreferredName().equals(name)) {
token = parser.nextToken();
if (token == Token.START_OBJECT) {
return PARSER.apply(parser, null).build(false);
} else {
throw new ParsingException(parser.getTokenLocation(), "unexpected token [" + token + "], expected [{, ]");
}
} else {
throw new ParsingException(
parser.getTokenLocation(),
"unexpected field [" + name + "], expected [" + SCRIPT_PARSE_FIELD.getPreferredName() + "]"
);
}
} 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" : "",
* "source" : "",
* "options" : {
* "option0" : "",
* "option1" : "",
* ...
* }
* }
* }
* }
*
* Note that the "source" parameter can also handle template parsing including from
* a complex JSON object.
*
* @param ignoreEmpty Specify as {@code true} to ignoreEmpty the empty source check.
* This allows empty templates to be loaded for backwards compatibility.
*/
public static StoredScriptSource fromXContent(XContentParser parser, boolean ignoreEmpty) {
return PARSER.apply(parser, null).build(ignoreEmpty);
}
/**
* Required for {@link ScriptMetadata.ScriptMetadataDiff}. Uses
* the {@link StoredScriptSource#StoredScriptSource(StreamInput)}
* constructor.
*/
public static Diff readDiffFrom(StreamInput in) throws IOException {
return readDiffFrom(StoredScriptSource::new, in);
}
private final String lang;
private final String source;
private final Map options;
/**
* Standard StoredScriptSource constructor.
* @param lang The language to compile the script with. Must not be {@code null}.
* @param source The source source 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 source, Map options) {
this.lang = Objects.requireNonNull(lang);
this.source = Objects.requireNonNull(source);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
}
/**
* Reads a {@link StoredScriptSource} from a stream. Version 5.3+ will read
* all of the lang, source, and options parameters. For versions prior to 5.3,
* only the source parameter will be read in as a bytes reference.
*/
public StoredScriptSource(StreamInput in) throws IOException {
this.lang = in.readString();
this.source = in.readString();
@SuppressWarnings("unchecked")
Map options = (Map) (Map) in.readMap();
this.options = options;
}
/**
* Writes a {@link StoredScriptSource} to a stream. Will write
* all of the lang, source, and options parameters.
*/
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(lang);
out.writeString(source);
@SuppressWarnings("unchecked")
Map options = (Map) (Map) this.options;
out.writeMap(options);
}
/**
* This will write XContent from a {@link StoredScriptSource}. The following format will be written:
*
* {@code
* {
* "script" : {
* "lang" : "",
* "source" : "",
* "options" : {
* "option0" : "",
* "option1" : "",
* ...
* }
* }
* }
* }
*
* Note that the 'source' 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(SOURCE_PARSE_FIELD.getPreferredName(), source);
if (options.isEmpty() == false) {
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 source used for compiling this script.
*/
public String getSource() {
return source;
}
/**
* @return The compiler options used for this script.
*/
public Map 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 (source != null ? !source.equals(that.source) : that.source != 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 + (source != null ? source.hashCode() : 0);
result = 31 * result + (options != null ? options.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "StoredScriptSource{" + "lang='" + lang + '\'' + ", source='" + source + '\'' + ", options=" + options + '}';
}
}