diff options
Diffstat (limited to 'core/src/main/java/org/elasticsearch/script')
5 files changed, 63 insertions, 231 deletions
diff --git a/core/src/main/java/org/elasticsearch/script/ScriptContext.java b/core/src/main/java/org/elasticsearch/script/ScriptContext.java index cc2408786e..cd3bff3379 100644 --- a/core/src/main/java/org/elasticsearch/script/ScriptContext.java +++ b/core/src/main/java/org/elasticsearch/script/ScriptContext.java @@ -19,87 +19,35 @@ package org.elasticsearch.script; -import org.elasticsearch.common.Strings; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** - * Context of an operation that uses scripts as part of its execution. + * A holder for information about a context in which a script is compiled and run. */ -public interface ScriptContext { - - /** - * @return the name of the operation - */ - String getKey(); - - /** - * Standard operations that make use of scripts as part of their execution. - * Note that the suggest api is considered part of search for simplicity, as well as the percolate api. - */ - enum Standard implements ScriptContext { - - AGGS("aggs"), SEARCH("search"), UPDATE("update"), INGEST("ingest"); - - private final String key; - - Standard(String key) { - this.key = key; - } - - @Override - public String getKey() { - return key; - } - - @Override - public String toString() { - return getKey(); - } +public final class ScriptContext { + + public static final ScriptContext AGGS = new ScriptContext("aggs"); + public static final ScriptContext SEARCH = new ScriptContext("search"); + public static final ScriptContext UPDATE = new ScriptContext("update"); + public static final ScriptContext INGEST = new ScriptContext("ingest"); + + public static final Map<String, ScriptContext> BUILTINS; + static { + Map<String, ScriptContext> builtins = new HashMap<>(); + builtins.put(AGGS.name, AGGS); + builtins.put(SEARCH.name, SEARCH); + builtins.put(UPDATE.name, UPDATE); + builtins.put(INGEST.name, INGEST); + BUILTINS = Collections.unmodifiableMap(builtins); } - /** - * Custom operation exposed via plugin, which makes use of scripts as part of its execution - */ - final class Plugin implements ScriptContext { - - private final String pluginName; - private final String operation; - private final String key; - - /** - * Creates a new custom scripts based operation exposed via plugin. - * The name of the plugin combined with the operation name can be used to enable/disable scripts via fine-grained settings. - * - * @param pluginName the name of the plugin - * @param operation the name of the operation - */ - public Plugin(String pluginName, String operation) { - if (Strings.hasLength(pluginName) == false) { - throw new IllegalArgumentException("plugin name cannot be empty when registering a custom script context"); - } - if (Strings.hasLength(operation) == false) { - throw new IllegalArgumentException("operation name cannot be empty when registering a custom script context"); - } - this.pluginName = pluginName; - this.operation = operation; - this.key = pluginName + "_" + operation; - } - - public String getPluginName() { - return pluginName; - } - - public String getOperation() { - return operation; - } - - @Override - public String getKey() { - return key; - } + /** A unique identifier for this context. */ + public final String name; - @Override - public String toString() { - return getKey(); - } + // pkg private ctor, only created by script module + public ScriptContext(String name) { + this.name = name; } } diff --git a/core/src/main/java/org/elasticsearch/script/ScriptContextRegistry.java b/core/src/main/java/org/elasticsearch/script/ScriptContextRegistry.java deleted file mode 100644 index 765be1d437..0000000000 --- a/core/src/main/java/org/elasticsearch/script/ScriptContextRegistry.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static java.util.Collections.unmodifiableMap; -import static java.util.Collections.unmodifiableSet; - -/** - * Registry for operations that use scripts as part of their execution. Can be standard operations of custom defined ones (via plugin). - * Allows plugins to register custom operations that they use scripts for, - * via {@link org.elasticsearch.plugins.ScriptPlugin} - * Scripts can be enabled/disabled via fine-grained settings for each single registered operation. - */ -public final class ScriptContextRegistry { - static final Set<String> RESERVED_SCRIPT_CONTEXTS = reservedScriptContexts(); - - private final Map<String, ScriptContext> scriptContexts; - - public ScriptContextRegistry(Collection<ScriptContext.Plugin> customScriptContexts) { - Map<String, ScriptContext> scriptContexts = new HashMap<>(); - for (ScriptContext.Standard scriptContext : ScriptContext.Standard.values()) { - scriptContexts.put(scriptContext.getKey(), scriptContext); - } - for (ScriptContext.Plugin customScriptContext : customScriptContexts) { - validateScriptContext(customScriptContext); - ScriptContext previousContext = scriptContexts.put(customScriptContext.getKey(), customScriptContext); - if (previousContext != null) { - throw new IllegalArgumentException("script context [" + customScriptContext.getKey() + "] cannot be registered twice"); - } - } - this.scriptContexts = unmodifiableMap(scriptContexts); - } - - /** - * @return a list that contains all the supported {@link ScriptContext}s, both standard ones and registered via plugins - */ - Collection<ScriptContext> scriptContexts() { - return scriptContexts.values(); - } - - /** - * @return <tt>true</tt> if the provided {@link ScriptContext} is supported, <tt>false</tt> otherwise - */ - boolean isSupportedContext(String scriptContext) { - return scriptContexts.containsKey(scriptContext); - } - - //script contexts can be used in fine-grained settings, we need to be careful with what we allow here - private void validateScriptContext(ScriptContext.Plugin scriptContext) { - if (RESERVED_SCRIPT_CONTEXTS.contains(scriptContext.getPluginName())) { - throw new IllegalArgumentException("[" + scriptContext.getPluginName() + "] is a reserved name, it cannot be registered as a custom script context"); - } - if (RESERVED_SCRIPT_CONTEXTS.contains(scriptContext.getOperation())) { - throw new IllegalArgumentException("[" + scriptContext.getOperation() + "] is a reserved name, it cannot be registered as a custom script context"); - } - } - - private static Set<String> reservedScriptContexts() { - Set<String> reserved = new HashSet<>(ScriptType.values().length + ScriptContext.Standard.values().length); - for (ScriptType scriptType : ScriptType.values()) { - reserved.add(scriptType.toString()); - } - for (ScriptContext.Standard scriptContext : ScriptContext.Standard.values()) { - reserved.add(scriptContext.getKey()); - } - reserved.add("script"); - reserved.add("engine"); - return unmodifiableSet(reserved); - } -} diff --git a/core/src/main/java/org/elasticsearch/script/ScriptEngine.java b/core/src/main/java/org/elasticsearch/script/ScriptEngine.java index caec832008..9572e891b3 100644 --- a/core/src/main/java/org/elasticsearch/script/ScriptEngine.java +++ b/core/src/main/java/org/elasticsearch/script/ScriptEngine.java @@ -37,13 +37,12 @@ public interface ScriptEngine extends Closeable { /** * Compiles a script. - * @param scriptName name of the script. {@code null} if it is anonymous (inline). - * For a file script, its the file name (with extension). - * For a stored script, its the identifier. - * @param scriptSource actual source of the script + * @param name the name of the script. {@code null} if it is anonymous (inline). For a stored script, its the identifier. + * @param code actual source of the script * @param params compile-time parameters (such as flags to the compiler) + * @return an opaque compiled script which may be cached and later passed to */ - Object compile(String scriptName, String scriptSource, Map<String, String> params); + Object compile(String name, String code, Map<String, String> params); ExecutableScript executable(CompiledScript compiledScript, @Nullable Map<String, Object> vars); diff --git a/core/src/main/java/org/elasticsearch/script/ScriptModule.java b/core/src/main/java/org/elasticsearch/script/ScriptModule.java index 7668b38c4c..b29a199c3a 100644 --- a/core/src/main/java/org/elasticsearch/script/ScriptModule.java +++ b/core/src/main/java/org/elasticsearch/script/ScriptModule.java @@ -19,17 +19,14 @@ package org.elasticsearch.script; -import org.elasticsearch.common.settings.ClusterSettings; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.plugins.ScriptPlugin; - -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.plugins.ScriptPlugin; /** * Manages building {@link ScriptService}. @@ -37,42 +34,26 @@ import java.util.stream.Collectors; public class ScriptModule { private final ScriptService scriptService; - /** - * Build from {@linkplain ScriptPlugin}s. Convenient for normal use but not great for tests. See - * {@link ScriptModule#ScriptModule(Settings, List, List)} for easier use in tests. - */ - public static ScriptModule create(Settings settings, List<ScriptPlugin> scriptPlugins) { - List<ScriptEngine> scriptEngines = scriptPlugins.stream().map(x -> x.getScriptEngine(settings)) - .filter(Objects::nonNull).collect(Collectors.toList()); - List<ScriptContext.Plugin> plugins = scriptPlugins.stream().map(x -> x.getCustomScriptContexts()).filter(Objects::nonNull) - .collect(Collectors.toList()); - return new ScriptModule(settings, scriptEngines, plugins); - } - - /** - * Build {@linkplain ScriptEngine} and {@linkplain ScriptContext.Plugin}. - */ - public ScriptModule(Settings settings, List<ScriptEngine> scriptEngines, - List<ScriptContext.Plugin> customScriptContexts) { - ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(customScriptContexts); - Map<String, ScriptEngine> enginesByName = getEnginesByName(scriptEngines); - try { - scriptService = new ScriptService(settings, enginesByName, scriptContextRegistry); - } catch (IOException e) { - throw new RuntimeException("Couldn't setup ScriptService", e); - } - } - - private Map<String, ScriptEngine> getEnginesByName(List<ScriptEngine> engines) { - Map<String, ScriptEngine> enginesByName = new HashMap<>(); - for (ScriptEngine engine : engines) { - ScriptEngine existing = enginesByName.put(engine.getType(), engine); - if (existing != null) { - throw new IllegalArgumentException("scripting language [" + engine.getType() + "] defined for engine [" + - existing.getClass().getName() + "] and [" + engine.getClass().getName()); + public ScriptModule(Settings settings, List<ScriptPlugin> scriptPlugins) { + Map<String, ScriptEngine> engines = new HashMap<>(); + Map<String, ScriptContext> contexts = new HashMap<>(ScriptContext.BUILTINS); + for (ScriptPlugin plugin : scriptPlugins) { + for (ScriptContext context : plugin.getContexts()) { + ScriptContext oldContext = contexts.put(context.name, context); + if (oldContext != null) { + throw new IllegalArgumentException("Context name [" + context.name + "] defined twice"); + } + } + ScriptEngine engine = plugin.getScriptEngine(settings); + if (engine != null) { + ScriptEngine existing = engines.put(engine.getType(), engine); + if (existing != null) { + throw new IllegalArgumentException("scripting language [" + engine.getType() + "] defined for engine [" + + existing.getClass().getName() + "] and [" + engine.getClass().getName()); + } } } - return Collections.unmodifiableMap(enginesByName); + scriptService = new ScriptService(settings, Collections.unmodifiableMap(engines), Collections.unmodifiableMap(contexts)); } /** diff --git a/core/src/main/java/org/elasticsearch/script/ScriptService.java b/core/src/main/java/org/elasticsearch/script/ScriptService.java index b681d07e98..656149be34 100644 --- a/core/src/main/java/org/elasticsearch/script/ScriptService.java +++ b/core/src/main/java/org/elasticsearch/script/ScriptService.java @@ -82,11 +82,10 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust private final Set<String> contextsAllowed; private final Map<String, ScriptEngine> engines; + private final Map<String, ScriptContext> contexts; private final Cache<CacheKey, CompiledScript> cache; - private final ScriptContextRegistry scriptContextRegistry; - private final ScriptMetrics scriptMetrics = new ScriptMetrics(); private ClusterState clusterState; @@ -96,12 +95,12 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust private double scriptsPerMinCounter; private double compilesAllowedPerNano; - public ScriptService(Settings settings, Map<String, ScriptEngine> engines, ScriptContextRegistry scriptContextRegistry) throws IOException { + public ScriptService(Settings settings, Map<String, ScriptEngine> engines, Map<String, ScriptContext> contexts) { super(settings); Objects.requireNonNull(settings); this.engines = Objects.requireNonNull(engines); - Objects.requireNonNull(scriptContextRegistry); + this.contexts = Objects.requireNonNull(contexts); if (Strings.hasLength(settings.get(DISABLE_DYNAMIC_SCRIPTING_SETTING))) { throw new IllegalArgumentException(DISABLE_DYNAMIC_SCRIPTING_SETTING + " is not a supported setting, replace with fine-grained script settings. \n" + @@ -166,7 +165,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust } } - if (scriptContextRegistry.isSupportedContext(settingContext)) { + if (contexts.containsKey(settingContext)) { this.contextsAllowed.add(settingContext); } else { throw new IllegalArgumentException( @@ -175,8 +174,6 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust } } - this.scriptContextRegistry = scriptContextRegistry; - int cacheMaxSize = SCRIPT_CACHE_SIZE_SETTING.get(settings); CacheBuilder<CacheKey, CompiledScript> cacheBuilder = CacheBuilder.builder(); @@ -223,9 +220,9 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust /** * Checks if a script can be executed and compiles it if needed, or returns the previously compiled and cached script. */ - public CompiledScript compile(Script script, ScriptContext scriptContext) { + public CompiledScript compile(Script script, ScriptContext context) { Objects.requireNonNull(script); - Objects.requireNonNull(scriptContext); + Objects.requireNonNull(context); ScriptType type = script.getType(); String lang = script.getLang(); @@ -266,10 +263,10 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust // TODO: fix this through some API or something, that's wrong // special exception to prevent expressions from compiling as update or mapping scripts boolean expression = "expression".equals(script.getLang()); - boolean notSupported = scriptContext.getKey().equals(ScriptContext.Standard.UPDATE.getKey()); + boolean notSupported = context.name.equals(ScriptContext.UPDATE.name); if (expression && notSupported) { throw new UnsupportedOperationException("scripts of type [" + script.getType() + "]," + - " operation [" + scriptContext.getKey() + "] and lang [" + lang + "] are not supported"); + " operation [" + context.name + "] and lang [" + lang + "] are not supported"); } ScriptEngine scriptEngine = getEngine(lang); @@ -278,12 +275,12 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust throw new IllegalArgumentException("cannot execute [" + type + "] scripts"); } - if (scriptContextRegistry.isSupportedContext(scriptContext.getKey()) == false) { - throw new IllegalArgumentException("script context [" + scriptContext.getKey() + "] not supported"); + if (contexts.containsKey(context.name) == false) { + throw new IllegalArgumentException("script context [" + context.name + "] not supported"); } - if (isContextEnabled(scriptContext) == false) { - throw new IllegalArgumentException("cannot execute scripts using [" + scriptContext.getKey() + "] context"); + if (isContextEnabled(context) == false) { + throw new IllegalArgumentException("cannot execute scripts using [" + context.name + "] context"); } if (logger.isTraceEnabled()) { @@ -380,7 +377,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust } public boolean isContextEnabled(ScriptContext scriptContext) { - return contextsAllowed == null || contextsAllowed.contains(scriptContext.getKey()); + return contextsAllowed == null || contextsAllowed.contains(scriptContext.name); } public boolean isAnyContextEnabled() { |