org.codelibs.elasticsearch.common.settings.Setting Maven / Gradle / Ivy
/*
* 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.codelibs.elasticsearch.common.settings;
import org.codelibs.elasticsearch.querybuilders.mock.log4j.Logger;
import org.codelibs.elasticsearch.ElasticsearchException;
import org.codelibs.elasticsearch.ElasticsearchParseException;
import org.codelibs.elasticsearch.action.support.ToXContentToBytes;
import org.codelibs.elasticsearch.common.Booleans;
import org.codelibs.elasticsearch.common.Nullable;
import org.codelibs.elasticsearch.common.Strings;
import org.codelibs.elasticsearch.common.collect.Tuple;
import org.codelibs.elasticsearch.common.logging.DeprecationLogger;
import org.codelibs.elasticsearch.common.logging.Loggers;
import org.codelibs.elasticsearch.common.regex.Regex;
import org.codelibs.elasticsearch.common.unit.ByteSizeValue;
import org.codelibs.elasticsearch.common.unit.TimeValue;
import org.codelibs.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.codelibs.elasticsearch.common.xcontent.XContentBuilder;
import org.codelibs.elasticsearch.common.xcontent.XContentFactory;
import org.codelibs.elasticsearch.common.xcontent.XContentParser;
import org.codelibs.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 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 {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 {Settings} object can be passed directly to the {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 extends ToXContentToBytes {
public enum Property {
/**
* should be filtered in some api (mask password/credentials)
*/
Filtered,
/**
* iff this setting is shared with more than one module ie. can be defined multiple times.
*/
Shared,
/**
* iff this setting can be dynamically updateable
*/
Dynamic,
/**
* 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 EnumSet properties;
private static final EnumSet EMPTY_PROPERTIES = EnumSet.noneOf(Property.class);
private Setting(Key key, @Nullable Setting fallbackSetting, Function defaultValue, Function parser,
Property... properties) {
assert parser.apply(defaultValue.apply(Settings.EMPTY)) != null || this.isGroupSetting(): "parser returned null";
this.key = key;
this.fallbackSetting = fallbackSetting;
this.defaultValue = defaultValue;
this.parser = parser;
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));
}
}
/**
* 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, null, defaultValue, parser, 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 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, 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 {Settings} object.
* Use {#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 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
if this setting is shared with more than one other module or plugin, otherwise false
*/
public boolean isShared() {
return properties.contains(Property.Shared);
}
/**
* Returns true
iff this setting is a group setting. Group settings represent a set of settings rather than a single value.
* The key, see {#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 final 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 final 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.get(getKey()) != null;
}
/**
* 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) {
String value = getRaw(settings);
try {
return parser.apply(value);
} 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) {
// 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 DeprecationLogger deprecationLogger = new DeprecationLogger(Loggers.getLogger(getClass()));
deprecationLogger.deprecated("[{}] setting was deprecated in Elasticsearch and it will be removed in a future release! " +
"See the breaking changes lists in the documentation for details", getKey());
}
return settings.get(getKey(), defaultValue.apply(settings));
}
/**
* 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;
}
/**
* 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 (fallbackSetting == null) {
return get(secondary);
}
if (exists(secondary)) {
return get(secondary);
}
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;
}
/**
* 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 eachother. See {AbstractScopedSettings#addSettingsUpdateConsumer(Setting, Setting, BiConsumer)}
* and its usage for details.
*/
static AbstractScopedSettings.SettingUpdater> compoundUpdater(final BiConsumer consumer,
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) {
return new Tuple<>(aSettingUpdater.getValue(current, previous), bSettingUpdater.getValue(current, previous));
}
@Override
public void apply(Tuple value, Settings current, Settings previous) {
if (aSettingUpdater.hasChanged(current, previous)) {
logger.info("updating [{}] from [{}] to [{}]", aSetting.key, aSetting.getRaw(previous), aSetting.getRaw(current));
}
if (bSettingUpdater.hasChanged(current, previous)) {
logger.info("updating [{}] from [{}] to [{}]", bSetting.key, bSetting.getRaw(previous), bSetting.getRaw(current));
}
consumer.accept(value.v1(), value.v2());
}
@Override
public String toString() {
return "CompoundUpdater for: " + aSettingUpdater + " and " + bSettingUpdater;
}
};
}
private final class Updater implements AbstractScopedSettings.SettingUpdater {
private final Consumer consumer;
private final Logger logger;
private final Consumer accept;
public Updater(Consumer consumer, Logger logger, Consumer accept) {
this.consumer = consumer;
this.logger = logger;
this.accept = accept;
}
@Override
public String toString() {
return "Updater for: " + Setting.this.toString();
}
@Override
public boolean hasChanged(Settings current, Settings previous) {
final String newValue = getRaw(current);
final String value = getRaw(previous);
assert isGroupSetting() == false : "group settings must override this method";
assert value != null : "value was null but can't be unless default is null which is invalid";
return value.equals(newValue) == false;
}
@Override
public T getValue(Settings current, Settings previous) {
final String newValue = getRaw(current);
final String value = getRaw(previous);
T inst = get(current);
try {
accept.accept(inst);
} catch (Exception | AssertionError e) {
throw new IllegalArgumentException("illegal value can't update [" + key + "] from [" + value + "] to [" + newValue + "]",
e);
}
return inst;
}
@Override
public void apply(T value, Settings current, Settings previous) {
logger.info("updating [{}] from [{}] to [{}]", key, getRaw(previous), getRaw(current));
consumer.accept(value);
}
}
public static Setting floatSetting(String key, float defaultValue, Property... properties) {
return new Setting<>(key, (s) -> Float.toString(defaultValue), Float::parseFloat, properties);
}
public static Setting floatSetting(String key, float defaultValue, float minValue, Property... properties) {
return new Setting<>(key, (s) -> Float.toString(defaultValue), (s) -> {
float value = Float.parseFloat(s);
if (value < minValue) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
return value;
}, properties);
}
public static Setting intSetting(String key, int defaultValue, int minValue, int maxValue, Property... properties) {
return new Setting<>(key, (s) -> Integer.toString(defaultValue), (s) -> parseInt(s, minValue, maxValue, key), properties);
}
public static Setting intSetting(String key, int defaultValue, int minValue, Property... properties) {
return new Setting<>(key, (s) -> Integer.toString(defaultValue), (s) -> parseInt(s, minValue, key), properties);
}
public static Setting intSetting(String key, Setting fallbackSetting, int minValue, Property... properties) {
return new Setting<>(key, fallbackSetting, (s) -> parseInt(s, minValue, key), properties);
}
public static Setting longSetting(String key, long defaultValue, long minValue, Property... properties) {
return new Setting<>(key, (s) -> Long.toString(defaultValue), (s) -> parseLong(s, minValue, key), properties);
}
public static Setting simpleString(String key, Property... properties) {
return new Setting<>(key, s -> "", Function.identity(), properties);
}
public static int parseInt(String s, int minValue, String key) {
return parseInt(s, minValue, Integer.MAX_VALUE, key);
}
public static int parseInt(String s, int minValue, int maxValue, String key) {
int value = Integer.parseInt(s);
if (value < minValue) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
if (value > maxValue) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be <= " + maxValue);
}
return value;
}
public static long parseLong(String s, long minValue, String key) {
long value = Long.parseLong(s);
if (value < minValue) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
return value;
}
public static TimeValue parseTimeValue(String s, TimeValue minValue, String key) {
TimeValue timeValue = TimeValue.parseTimeValue(s, null, key);
if (timeValue.millis() < minValue.millis()) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
return timeValue;
}
public static Setting intSetting(String key, int defaultValue, Property... properties) {
return intSetting(key, defaultValue, Integer.MIN_VALUE, properties);
}
public static Setting boolSetting(String key, boolean defaultValue, Property... properties) {
return new Setting<>(key, (s) -> Boolean.toString(defaultValue), Booleans::parseBooleanExact, properties);
}
public static Setting boolSetting(String key, Setting fallbackSetting, Property... properties) {
return new Setting<>(key, fallbackSetting, Booleans::parseBooleanExact, properties);
}
public static Setting boolSetting(String key, Function defaultValueFn, Property... properties) {
return new Setting<>(key, defaultValueFn, Booleans::parseBooleanExact, properties);
}
public static Setting byteSizeSetting(String key, ByteSizeValue value, Property... properties) {
return byteSizeSetting(key, (s) -> value.toString(), properties);
}
public static Setting byteSizeSetting(String key, Setting fallbackSetting,
Property... properties) {
return new Setting<>(key, fallbackSetting, (s) -> ByteSizeValue.parseBytesSizeValue(s, key), properties);
}
public static Setting byteSizeSetting(String key, Function defaultValue,
Property... properties) {
return new Setting<>(key, defaultValue, (s) -> ByteSizeValue.parseBytesSizeValue(s, key), properties);
}
public static Setting byteSizeSetting(String key, ByteSizeValue defaultValue, ByteSizeValue minValue,
ByteSizeValue maxValue, Property... properties) {
return byteSizeSetting(key, (s) -> defaultValue.toString(), minValue, maxValue, properties);
}
public static Setting byteSizeSetting(String key, Function defaultValue,
ByteSizeValue minValue, ByteSizeValue maxValue,
Property... properties) {
return new Setting<>(key, defaultValue, (s) -> parseByteSize(s, minValue, maxValue, key), properties);
}
public static ByteSizeValue parseByteSize(String s, ByteSizeValue minValue, ByteSizeValue maxValue, String key) {
ByteSizeValue value = ByteSizeValue.parseBytesSizeValue(s, key);
if (value.getBytes() < minValue.getBytes()) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
if (value.getBytes() > maxValue.getBytes()) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be <= " + maxValue);
}
return value;
}
/**
* Creates a setting which specifies a memory size. This can either be
* specified as an absolute bytes value or as a percentage of the heap
* memory.
*
* @param key the key for the setting
* @param defaultValue the default value for this setting
* @param properties properties properties for this setting like scope, filtering...
* @return the setting object
*/
public static Setting memorySizeSetting(String key, ByteSizeValue defaultValue, Property... properties) {
return memorySizeSetting(key, (s) -> defaultValue.toString(), properties);
}
/**
* Creates a setting which specifies a memory size. This can either be
* specified as an absolute bytes value or as a percentage of the heap
* memory.
*
* @param key the key for the setting
* @param defaultValue a function that supplies the default value for this setting
* @param properties properties properties for this setting like scope, filtering...
* @return the setting object
*/
public static Setting memorySizeSetting(String key, Function defaultValue, Property... properties) {
throw new UnsupportedOperationException();
}
/**
* Creates a setting which specifies a memory size. This can either be
* specified as an absolute bytes value or as a percentage of the heap
* memory.
*
* @param key the key for the setting
* @param defaultPercentage the default value of this setting as a percentage of the heap memory
* @param properties properties properties for this setting like scope, filtering...
* @return the setting object
*/
public static Setting memorySizeSetting(String key, String defaultPercentage, Property... properties) {
throw new UnsupportedOperationException();
}
public static Setting> listSetting(String key, List defaultStringValue, Function singleValueParser,
Property... properties) {
return listSetting(key, (s) -> defaultStringValue, singleValueParser, properties);
}
// TODO this one's two argument get is still broken
public static Setting> listSetting(String key, Setting> fallbackSetting, Function singleValueParser,
Property... properties) {
return listSetting(key, (s) -> parseableStringToList(fallbackSetting.getRaw(s)), singleValueParser, properties);
}
public static Setting> listSetting(String key, Function> defaultStringValue,
Function singleValueParser, Property... properties) {
if (defaultStringValue.apply(Settings.EMPTY) == null) {
throw new IllegalArgumentException("default value function must not return null");
}
Function> parser = (s) ->
parseableStringToList(s).stream().map(singleValueParser).collect(Collectors.toList());
return new Setting>(new ListKey(key),
(s) -> arrayToParsableString(defaultStringValue.apply(s).toArray(Strings.EMPTY_ARRAY)), parser, properties) {
@Override
public String getRaw(Settings settings) {
String[] array = settings.getAsArray(getKey(), null);
return array == null ? defaultValue.apply(settings) : arrayToParsableString(array);
}
@Override
boolean hasComplexMatcher() {
return true;
}
@Override
public boolean exists(Settings settings) {
boolean exists = super.exists(settings);
return exists || settings.get(getKey() + ".0") != null;
}
@Override
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
if (exists(source) == false) {
String[] asArray = defaultSettings.getAsArray(getKey(), null);
if (asArray == null) {
builder.putArray(getKey(), defaultStringValue.apply(defaultSettings));
} else {
builder.putArray(getKey(), asArray);
}
}
}
};
}
private static List parseableStringToList(String parsableString) {
// EMPTY is safe here because we never call namedObject
try (XContentParser xContentParser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, parsableString)) {
XContentParser.Token token = xContentParser.nextToken();
if (token != XContentParser.Token.START_ARRAY) {
throw new IllegalArgumentException("expected START_ARRAY but got " + token);
}
ArrayList list = new ArrayList<>();
while ((token = xContentParser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token != XContentParser.Token.VALUE_STRING) {
throw new IllegalArgumentException("expected VALUE_STRING but got " + token);
}
list.add(xContentParser.text());
}
return list;
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse array", e);
}
}
private static String arrayToParsableString(String[] array) {
try {
XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent());
builder.startArray();
for (String element : array) {
builder.value(element);
}
builder.endArray();
return builder.string();
} catch (IOException ex) {
throw new ElasticsearchException(ex);
}
}
public static Setting groupSetting(String key, Property... properties) {
return groupSetting(key, (s) -> {}, properties);
}
public static Setting groupSetting(String key, Consumer validator, Property... properties) {
return new Setting(new GroupKey(key), (s) -> "", (s) -> null, properties) {
@Override
public boolean isGroupSetting() {
return true;
}
@Override
public String getRaw(Settings settings) {
Settings subSettings = get(settings);
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
subSettings.toXContent(builder, EMPTY_PARAMS);
builder.endObject();
return builder.string();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Settings get(Settings settings) {
Settings byPrefix = settings.getByPrefix(getKey());
validator.accept(byPrefix);
return byPrefix;
}
@Override
public boolean exists(Settings settings) {
for (Map.Entry entry : settings.getAsMap().entrySet()) {
if (entry.getKey().startsWith(key)) {
return true;
}
}
return false;
}
@Override
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
Map leftGroup = get(source).getAsMap();
Settings defaultGroup = get(defaultSettings);
for (Map.Entry entry : defaultGroup.getAsMap().entrySet()) {
if (leftGroup.containsKey(entry.getKey()) == false) {
builder.put(getKey() + entry.getKey(), entry.getValue());
}
}
}
@Override
public AbstractScopedSettings.SettingUpdater newUpdater(Consumer consumer, Logger logger,
Consumer validator) {
if (isDynamic() == false) {
throw new IllegalStateException("setting [" + getKey() + "] is not dynamic");
}
final Setting> setting = this;
return new AbstractScopedSettings.SettingUpdater() {
@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) {
Settings currentSettings = get(current);
Settings previousSettings = get(previous);
try {
validator.accept(currentSettings);
} catch (Exception | AssertionError e) {
throw new IllegalArgumentException("illegal value can't update [" + key + "] from ["
+ previousSettings.getAsMap() + "] to [" + currentSettings.getAsMap() + "]", e);
}
return currentSettings;
}
@Override
public void apply(Settings value, Settings current, Settings previous) {
if (logger.isInfoEnabled()) { // getRaw can create quite some objects
logger.info("updating [{}] from [{}] to [{}]", key, getRaw(previous), getRaw(current));
}
consumer.accept(value);
}
@Override
public String toString() {
return "Updater for: " + setting.toString();
}
};
}
};
}
public static Setting timeSetting(String key, Function defaultValue, TimeValue minValue,
Property... properties) {
return new Setting<>(key, (s) -> defaultValue.apply(s).getStringRep(), (s) -> {
TimeValue timeValue = TimeValue.parseTimeValue(s, null, key);
if (timeValue.millis() < minValue.millis()) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
return timeValue;
}, properties);
}
public static Setting timeSetting(String key, TimeValue defaultValue, TimeValue minValue, Property... properties) {
return timeSetting(key, (s) -> defaultValue, minValue, properties);
}
public static Setting timeSetting(String key, TimeValue defaultValue, Property... properties) {
return new Setting<>(key, (s) -> defaultValue.getStringRep(), (s) -> TimeValue.parseTimeValue(s, key), properties);
}
public static Setting timeSetting(String key, Setting fallbackSetting, Property... properties) {
return new Setting<>(key, fallbackSetting, (s) -> TimeValue.parseTimeValue(s, key), properties);
}
public static Setting positiveTimeSetting(String key, TimeValue defaultValue, Property... properties) {
return timeSetting(key, defaultValue, TimeValue.timeValueMillis(0), properties);
}
public static Setting doubleSetting(String key, double defaultValue, double minValue, Property... properties) {
return new Setting<>(key, (s) -> Double.toString(defaultValue), (s) -> {
final double d = Double.parseDouble(s);
if (d < minValue) {
throw new IllegalArgumentException("Failed to parse value [" + s + "] for setting [" + key + "] must be >= " + minValue);
}
return d;
}, properties);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Setting> setting = (Setting>) o;
return Objects.equals(key, setting.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
/**
* This setting type allows to validate settings that have the same type and a common prefix. For instance feature.${type}=[true|false]
* can easily be added with this setting. Yet, prefix key settings don't support updaters out of the box unless
* {#getConcreteSetting(String)} is used to pull the updater.
*/
public static Setting prefixKeySetting(String prefix, String defaultValue, Function parser,
Property... properties) {
return affixKeySetting(AffixKey.withPrefix(prefix), (s) -> defaultValue, parser, properties);
}
/**
* This setting type allows to validate settings that have the same type and a common prefix and suffix. For instance
* storage.${backend}.enable=[true|false] can easily be added with this setting. Yet, adfix key settings don't support updaters
* out of the box unless {#getConcreteSetting(String)} is used to pull the updater.
*/
public static Setting affixKeySetting(String prefix, String suffix, Function defaultValue,
Function parser, Property... properties) {
return affixKeySetting(AffixKey.withAffix(prefix, suffix), defaultValue, parser, properties);
}
public static Setting affixKeySetting(String prefix, String suffix, String defaultValue, Function parser,
Property... properties) {
return affixKeySetting(prefix, suffix, (s) -> defaultValue, parser, properties);
}
public static Setting affixKeySetting(AffixKey key, Function defaultValue, Function parser,
Property... properties) {
return new Setting(key, defaultValue, parser, properties) {
@Override
boolean isGroupSetting() {
return true;
}
@Override
AbstractScopedSettings.SettingUpdater newUpdater(Consumer consumer, Logger logger, Consumer validator) {
throw new UnsupportedOperationException("Affix settings can't be updated. Use #getConcreteSetting for updating.");
}
@Override
public Setting getConcreteSetting(String key) {
if (match(key)) {
return new Setting<>(key, defaultValue, parser, properties);
} else {
throw new IllegalArgumentException("key [" + key + "] must match [" + getKey() + "] but didn't.");
}
}
@Override
public void diff(Settings.Builder builder, Settings source, Settings defaultSettings) {
for (Map.Entry entry : defaultSettings.getAsMap().entrySet()) {
if (match(entry.getKey())) {
getConcreteSetting(entry.getKey()).diff(builder, source, defaultSettings);
}
}
}
};
}
public interface Key {
boolean match(String key);
}
public static class SimpleKey implements Key {
protected final String key;
public SimpleKey(String key) {
this.key = key;
}
@Override
public boolean match(String key) {
return this.key.equals(key);
}
@Override
public String toString() {
return key;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SimpleKey simpleKey = (SimpleKey) o;
return Objects.equals(key, simpleKey.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
}
public static final class GroupKey extends SimpleKey {
public GroupKey(String key) {
super(key);
if (key.endsWith(".") == false) {
throw new IllegalArgumentException("key must end with a '.'");
}
}
@Override
public boolean match(String toTest) {
return Regex.simpleMatch(key + "*", toTest);
}
}
public static final class ListKey extends SimpleKey {
private final Pattern pattern;
public ListKey(String key) {
super(key);
this.pattern = Pattern.compile(Pattern.quote(key) + "(\\.\\d+)?");
}
@Override
public boolean match(String toTest) {
return pattern.matcher(toTest).matches();
}
}
public static final class AffixKey implements Key {
public static AffixKey withPrefix(String prefix) {
return new AffixKey(prefix, null);
}
public static AffixKey withAffix(String prefix, String suffix) {
return new AffixKey(prefix, suffix);
}
private final String prefix;
private final String suffix;
public AffixKey(String prefix, String suffix) {
assert prefix != null || suffix != null: "Either prefix or suffix must be non-null";
this.prefix = prefix;
if (prefix.endsWith(".") == false) {
throw new IllegalArgumentException("prefix must end with a '.'");
}
this.suffix = suffix;
}
@Override
public boolean match(String key) {
boolean match = true;
if (prefix != null) {
match = key.startsWith(prefix);
}
if (suffix != null) {
match = match && key.endsWith(suffix);
}
return match;
}
public SimpleKey toConcreteKey(String missingPart) {
StringBuilder key = new StringBuilder();
if (prefix != null) {
key.append(prefix);
}
key.append(missingPart);
if (suffix != null) {
key.append(".");
key.append(suffix);
}
return new SimpleKey(key.toString());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
}
if (suffix != null) {
sb.append('*');
sb.append('.');
sb.append(suffix);
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AffixKey that = (AffixKey) o;
return Objects.equals(prefix, that.prefix) &&
Objects.equals(suffix, that.suffix);
}
@Override
public int hashCode() {
return Objects.hash(prefix, suffix);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy