
org.elasticsearch.common.settings.Setting 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 - Open Source, Distributed, RESTful Search Engine
/*
* 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.common.settings;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.MemorySizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
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.XContentType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A setting. Encapsulates typical stuff like default value, parsing, and scope.
* Some (SettingsProperty.Dynamic) can by modified at run time using the API.
* All settings inside elasticsearch or in any of the plugins should use this type-safe and generic settings infrastructure
* together with {@link AbstractScopedSettings}. This class contains several utility methods that makes it straight forward
* to add settings for the majority of the cases. For instance a simple boolean settings can be defined like this:
* {@code
* public static final Setting; MY_BOOLEAN = Setting.boolSetting("my.bool.setting", true, SettingsProperty.NodeScope);}
*
* To retrieve the value of the setting a {@link Settings} object can be passed directly to the {@link Setting#get(Settings)} method.
*
* final boolean myBooleanValue = MY_BOOLEAN.get(settings);
*
* It's recommended to use typed settings rather than string based settings. For example adding a setting for an enum type:
* {@code
* public enum Color {
* RED, GREEN, BLUE;
* }
* public static final Setting MY_BOOLEAN =
* new Setting<>("my.color.setting", Color.RED.toString(), Color::valueOf, SettingsProperty.NodeScope);
* }
*
*/
public class Setting implements ToXContentObject {
public enum Property {
/**
* should be filtered in some api (mask password/credentials)
*/
Filtered,
/**
* iff this setting can be dynamically updateable
*/
Dynamic,
/**
* mark this setting as final, not updateable even when the context is not dynamic
* ie. Setting this property on an index scoped setting will fail update when the index is closed
*/
Final,
/**
* mark this setting as deprecated
*/
Deprecated,
/**
* Node scope
*/
NodeScope,
/**
* Index scope
*/
IndexScope
}
private final Key key;
protected final Function defaultValue;
@Nullable
private final Setting fallbackSetting;
private final Function parser;
private final Validator validator;
private final EnumSet properties;
private static final EnumSet EMPTY_PROPERTIES = EnumSet.noneOf(Property.class);
private Setting(Key key, @Nullable Setting fallbackSetting, Function defaultValue, Function parser,
Validator validator, Property... properties) {
assert this instanceof SecureSetting || this.isGroupSetting() || parser.apply(defaultValue.apply(Settings.EMPTY)) != null
: "parser returned null";
this.key = key;
this.fallbackSetting = fallbackSetting;
this.defaultValue = defaultValue;
this.parser = parser;
this.validator = validator;
if (properties == null) {
throw new IllegalArgumentException("properties cannot be null for setting [" + key + "]");
}
if (properties.length == 0) {
this.properties = EMPTY_PROPERTIES;
} else {
this.properties = EnumSet.copyOf(Arrays.asList(properties));
if (isDynamic() && isFinal()) {
throw new IllegalArgumentException("final setting [" + key + "] cannot be dynamic");
}
}
}
/**
* Creates a new Setting instance
* @param key the settings key for this setting.
* @param defaultValue a default value function that returns the default values string representation.
* @param parser a parser that parses the string rep into a complex datatype.
* @param properties properties for this setting like scope, filtering...
*/
public Setting(Key key, Function defaultValue, Function parser, Property... properties) {
this(key, defaultValue, parser, (v, s) -> {}, properties);
}
/**
* Creates a new {@code Setting} instance.
*
* @param key the settings key for this setting
* @param defaultValue a default value function that results a string representation of the default value
* @param parser a parser that parses a string representation into the concrete type for this setting
* @param validator a {@link Validator} for validating this setting
* @param properties properties for this setting
*/
public Setting(
Key key, Function defaultValue, Function parser, Validator validator, Property... properties) {
this(key, null, defaultValue, parser, validator, properties);
}
/**
* Creates a new Setting instance
* @param key the settings key for this setting.
* @param defaultValue a default value.
* @param parser a parser that parses the string rep into a complex datatype.
* @param properties properties for this setting like scope, filtering...
*/
public Setting(String key, String defaultValue, Function parser, Property... properties) {
this(key, s -> defaultValue, parser, properties);
}
/**
* Creates a new {@code Setting} instance.
*
* @param key the settings key for this setting
* @param defaultValue a default value function that results a string representation of the default value
* @param parser a parser that parses a string representation into the concrete type for this setting
* @param validator a {@link Validator} for validating this setting
* @param properties properties for this setting
*/
public Setting(String key, String defaultValue, Function parser, Validator validator, Property... properties) {
this(new SimpleKey(key), s -> defaultValue, parser, validator, properties);
}
/**
* Creates a new Setting instance
* @param key the settings key for this setting.
* @param defaultValue a default value function that returns the default values string representation.
* @param parser a parser that parses the string rep into a complex datatype.
* @param properties properties for this setting like scope, filtering...
*/
public Setting(String key, Function defaultValue, Function parser, Property... properties) {
this(new SimpleKey(key), defaultValue, parser, properties);
}
/**
* Creates a new Setting instance
* @param key the settings key for this setting.
* @param fallbackSetting a setting who's value to fallback on if this setting is not defined
* @param parser a parser that parses the string rep into a complex datatype.
* @param properties properties for this setting like scope, filtering...
*/
public Setting(Key key, Setting fallbackSetting, Function parser, Property... properties) {
this(key, fallbackSetting, fallbackSetting::getRaw, parser, (v, m) -> {}, properties);
}
/**
* Creates a new Setting instance
* @param key the settings key for this setting.
* @param fallBackSetting a setting to fall back to if the current setting is not set.
* @param parser a parser that parses the string rep into a complex datatype.
* @param properties properties for this setting like scope, filtering...
*/
public Setting(String key, Setting fallBackSetting, Function parser, Property... properties) {
this(new SimpleKey(key), fallBackSetting, parser, properties);
}
/**
* Returns the settings key or a prefix if this setting is a group setting.
* Note: this method should not be used to retrieve a value from a {@link Settings} object.
* Use {@link #get(Settings)} instead
*
* @see #isGroupSetting()
*/
public final String getKey() {
return key.toString();
}
/**
* Returns the original representation of a setting key.
*/
public final Key getRawKey() {
return key;
}
/**
* Returns true
if this setting is dynamically updateable, otherwise false
*/
public final boolean isDynamic() {
return properties.contains(Property.Dynamic);
}
/**
* Returns true
if this setting is final, otherwise false
*/
public final boolean isFinal() {
return properties.contains(Property.Final);
}
/**
* Returns the setting properties
* @see Property
*/
public EnumSet getProperties() {
return properties;
}
/**
* Returns true
if this setting must be filtered, otherwise false
*/
public boolean isFiltered() {
return properties.contains(Property.Filtered);
}
/**
* Returns true
if this setting has a node scope, otherwise false
*/
public boolean hasNodeScope() {
return properties.contains(Property.NodeScope);
}
/**
* Returns true
if this setting has an index scope, otherwise false
*/
public boolean hasIndexScope() {
return properties.contains(Property.IndexScope);
}
/**
* Returns true
if this setting is deprecated, otherwise false
*/
public boolean isDeprecated() {
return properties.contains(Property.Deprecated);
}
/**
* Returns true
iff this setting is a group setting. Group settings represent a set of settings rather than a single value.
* The key, see {@link #getKey()}, in contrast to non-group settings is a prefix like cluster.store. that matches all settings
* with this prefix.
*/
boolean isGroupSetting() {
return false;
}
boolean hasComplexMatcher() {
return isGroupSetting();
}
/**
* Returns the default value string representation for this setting.
* @param settings a settings object for settings that has a default value depending on another setting if available
*/
public String getDefaultRaw(Settings settings) {
return defaultValue.apply(settings);
}
/**
* Returns the default value for this setting.
* @param settings a settings object for settings that has a default value depending on another setting if available
*/
public T getDefault(Settings settings) {
return parser.apply(getDefaultRaw(settings));
}
/**
* Returns true
iff this setting is present in the given settings object. Otherwise false
*/
public boolean exists(Settings settings) {
return settings.keySet().contains(getKey());
}
/**
* Returns the settings value. If the setting is not present in the given settings object the default value is returned
* instead.
*/
public T get(Settings settings) {
return get(settings, true);
}
private T get(Settings settings, boolean validate) {
String value = getRaw(settings);
try {
T parsed = parser.apply(value);
if (validate) {
final Iterator> it = validator.settings();
final Map, T> map;
if (it.hasNext()) {
map = new HashMap<>();
while (it.hasNext()) {
final Setting setting = it.next();
map.put(setting, setting.get(settings, false)); // we have to disable validation or we will stack overflow
}
} else {
map = Collections.emptyMap();
}
validator.validate(parsed, map);
}
return parsed;
} catch (ElasticsearchParseException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Failed to parse value [" + value + "] for setting [" + getKey() + "]", ex);
} catch (IllegalArgumentException ex) {
throw ex;
} catch (Exception t) {
throw new IllegalArgumentException("Failed to parse value [" + value + "] for setting [" + getKey() + "]", t);
}
}
/**
* Add this setting to the builder if it doesn't exists in the source settings.
* The value added to the builder is taken from the given default settings object.
* @param builder the settings builder to fill the diff into
* @param source the source settings object to diff
* @param defaultSettings the default settings object to diff against
*/
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
if (exists(source) == false) {
builder.put(getKey(), getRaw(defaultSettings));
}
}
/**
* Returns the raw (string) settings value. If the setting is not present in the given settings object the default value is returned
* instead. This is useful if the value can't be parsed due to an invalid value to access the actual value.
*/
public String getRaw(Settings settings) {
checkDeprecation(settings);
return settings.get(getKey(), defaultValue.apply(settings));
}
/** Logs a deprecation warning if the setting is deprecated and used. */
void checkDeprecation(Settings settings) {
// They're using the setting, so we need to tell them to stop
if (this.isDeprecated() && this.exists(settings)) {
// It would be convenient to show its replacement key, but replacement is often not so simple
final String key = getKey();
Settings.DeprecationLoggerHolder.deprecationLogger.deprecatedAndMaybeLog(
key,
"[{}] setting was deprecated in Elasticsearch and will be removed in a future release! "
+ "See the breaking changes documentation for the next major version.",
key);
}
}
/**
* Returns true
iff the given key matches the settings key or if this setting is a group setting if the
* given key is part of the settings group.
* @see #isGroupSetting()
*/
public final boolean match(String toTest) {
return key.match(toTest);
}
@Override
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("key", key.toString());
builder.field("properties", properties);
builder.field("is_group_setting", isGroupSetting());
builder.field("default", defaultValue.apply(Settings.EMPTY));
builder.endObject();
return builder;
}
@Override
public String toString() {
return Strings.toString(this, true, true);
}
/**
* Returns the value for this setting but falls back to the second provided settings object
*/
public final T get(Settings primary, Settings secondary) {
if (exists(primary)) {
return get(primary);
}
if (exists(secondary)) {
return get(secondary);
}
if (fallbackSetting == null) {
return get(primary);
}
if (fallbackSetting.exists(primary)) {
return fallbackSetting.get(primary);
}
return fallbackSetting.get(secondary);
}
public Setting getConcreteSetting(String key) {
// we use startsWith here since the key might be foo.bar.0 if it's an array
assert key.startsWith(this.getKey()) : "was " + key + " expected: " + getKey();
return this;
}
/**
* Returns a set of settings that are required at validation time. Unless all of the dependencies are present in the settings
* object validation of setting must fail.
*/
public Set getSettingsDependencies(String key) {
return Collections.emptySet();
}
/**
* Build a new updater with a noop validator.
*/
final AbstractScopedSettings.SettingUpdater newUpdater(Consumer consumer, Logger logger) {
return newUpdater(consumer, logger, (s) -> {});
}
/**
* Build the updater responsible for validating new values, logging the new
* value, and eventually setting the value where it belongs.
*/
AbstractScopedSettings.SettingUpdater newUpdater(Consumer consumer, Logger logger, Consumer validator) {
if (isDynamic()) {
return new Updater(consumer, logger, validator);
} else {
throw new IllegalStateException("setting [" + getKey() + "] is not dynamic");
}
}
/**
* Updates settings that depend on each other.
* See {@link AbstractScopedSettings#addSettingsUpdateConsumer(Setting, Setting, BiConsumer)} and its usage for details.
*/
static AbstractScopedSettings.SettingUpdater> compoundUpdater(final BiConsumer consumer,
final BiConsumer validator, final Setting aSetting, final Setting bSetting, Logger logger) {
final AbstractScopedSettings.SettingUpdater aSettingUpdater = aSetting.newUpdater(null, logger);
final AbstractScopedSettings.SettingUpdater bSettingUpdater = bSetting.newUpdater(null, logger);
return new AbstractScopedSettings.SettingUpdater>() {
@Override
public boolean hasChanged(Settings current, Settings previous) {
return aSettingUpdater.hasChanged(current, previous) || bSettingUpdater.hasChanged(current, previous);
}
@Override
public Tuple getValue(Settings current, Settings previous) {
A valueA = aSettingUpdater.getValue(current, previous);
B valueB = bSettingUpdater.getValue(current, previous);
validator.accept(valueA, valueB);
return new Tuple<>(valueA, valueB);
}
@Override
public void apply(Tuple value, Settings current, Settings previous) {
if (aSettingUpdater.hasChanged(current, previous)) {
logSettingUpdate(aSetting, current, previous, logger);
}
if (bSettingUpdater.hasChanged(current, previous)) {
logSettingUpdate(bSetting, current, previous, logger);
}
consumer.accept(value.v1(), value.v2());
}
@Override
public String toString() {
return "CompoundUpdater for: " + aSettingUpdater + " and " + bSettingUpdater;
}
};
}
static AbstractScopedSettings.SettingUpdater groupedSettingsUpdater(Consumer consumer, Logger logger,
final List extends Setting>> configuredSettings) {
return new AbstractScopedSettings.SettingUpdater() {
private Settings get(Settings settings) {
return settings.filter(s -> {
for (Setting> setting : configuredSettings) {
if (setting.key.match(s)) {
return true;
}
}
return false;
});
}
@Override
public boolean hasChanged(Settings current, Settings previous) {
Settings currentSettings = get(current);
Settings previousSettings = get(previous);
return currentSettings.equals(previousSettings) == false;
}
@Override
public Settings getValue(Settings current, Settings previous) {
return get(current);
}
@Override
public void apply(Settings value, Settings current, Settings previous) {
consumer.accept(value);
}
@Override
public String toString() {
return "Updater grouped: " + configuredSettings.stream().map(Setting::getKey).collect(Collectors.joining(", "));
}
};
}
public static class AffixSetting extends Setting {
private final AffixKey key;
private final Function> delegateFactory;
private final Set dependencies;
public AffixSetting(AffixKey key, Setting delegate, Function> delegateFactory, AffixSetting... dependencies) {
super(key, delegate.defaultValue, delegate.parser, delegate.properties.toArray(new Property[0]));
this.key = key;
this.delegateFactory = delegateFactory;
this.dependencies = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(dependencies)));
}
boolean isGroupSetting() {
return true;
}
private Stream matchStream(Settings settings) {
return settings.keySet().stream().filter(this::match).map(key::getConcreteString);
}
public Set getSettingsDependencies(String settingsKey) {
if (dependencies.isEmpty()) {
return Collections.emptySet();
} else {
String namespace = key.getNamespace(settingsKey);
return dependencies.stream().map(s -> s.key.toConcreteKey(namespace).key).collect(Collectors.toSet());
}
}
AbstractScopedSettings.SettingUpdater
© 2015 - 2025 Weber Informatics LLC | Privacy Policy