Please wait. This can take some minutes ...
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.
org.opensearch.common.settings.Settings Maven / Gradle / Ivy
/*
* 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.common.settings;
import org.apache.logging.log4j.Level;
import org.opensearch.OpenSearchGenerationException;
import org.opensearch.OpenSearchParseException;
import org.opensearch.Version;
import org.opensearch.common.Booleans;
import org.opensearch.common.SetOnce;
import org.opensearch.common.annotation.PublicApi;
import org.opensearch.common.logging.DeprecationLogger;
import org.opensearch.common.logging.LogConfigurator;
import org.opensearch.common.unit.MemorySizeValue;
import org.opensearch.common.unit.TimeValue;
import org.opensearch.common.util.io.IOUtils;
import org.opensearch.common.xcontent.LoggingDeprecationHandler;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.common.Strings;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.common.settings.SecureString;
import org.opensearch.core.common.unit.ByteSizeUnit;
import org.opensearch.core.common.unit.ByteSizeValue;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.MediaTypeRegistry;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.ToXContentFragment;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.core.xcontent.XContentParserUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.opensearch.common.settings.AbstractScopedSettings.ARCHIVED_SETTINGS_PREFIX;
import static org.opensearch.common.unit.TimeValue.parseTimeValue;
import static org.opensearch.core.common.unit.ByteSizeValue.parseBytesSizeValue;
/**
* An immutable settings implementation.
*
* @opensearch.api
*/
@PublicApi(since = "1.0.0")
public final class Settings implements ToXContentFragment {
public static final Settings EMPTY = new Settings(Collections.emptyMap(), null);
/** The raw settings from the full key to raw string value. */
private final Map settings;
/** The secure settings storage associated with these settings. */
private final SecureSettings secureSettings;
/** The first level of setting names. This is constructed lazily in {@link #names()}. */
private final SetOnce> firstLevelNames = new SetOnce<>();
/**
* Setting names found in this Settings for both string and secure settings.
* This is constructed lazily in {@link #keySet()}.
*/
private final SetOnce> keys = new SetOnce<>();
private Settings(Map settings, SecureSettings secureSettings) {
// we use a sorted map for consistent serialization when using getAsMap()
this.settings = Collections.unmodifiableSortedMap(new TreeMap<>(settings));
this.secureSettings = secureSettings;
}
/**
* Retrieve the secure settings in these settings.
*/
SecureSettings getSecureSettings() {
// pkg private so it can only be accessed by local subclasses of SecureSetting
return secureSettings;
}
private Map getAsStructuredMap() {
Map map = new HashMap<>(2);
for (Map.Entry entry : settings.entrySet()) {
processSetting(map, "", entry.getKey(), entry.getValue());
}
for (Map.Entry entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map valMap = (Map) entry.getValue();
entry.setValue(convertMapsToArrays(valMap));
}
}
return map;
}
private void processSetting(Map map, String prefix, String setting, Object value) {
int prefixLength = setting.indexOf('.');
if (prefixLength == -1) {
@SuppressWarnings("unchecked")
Map innerMap = (Map) map.get(prefix + setting);
if (innerMap != null) {
// It supposed to be a value, but we already have a map stored, need to convert this map to "." notation
for (Map.Entry entry : innerMap.entrySet()) {
map.put(prefix + setting + "." + entry.getKey(), entry.getValue());
}
}
map.put(prefix + setting, value);
} else {
String key = setting.substring(0, prefixLength);
String rest = setting.substring(prefixLength + 1);
Object existingValue = map.get(prefix + key);
if (existingValue == null) {
Map newMap = new HashMap<>(2);
processSetting(newMap, "", rest, value);
map.put(prefix + key, newMap);
} else {
if (existingValue instanceof Map) {
@SuppressWarnings("unchecked")
Map innerMap = (Map) existingValue;
processSetting(innerMap, "", rest, value);
map.put(prefix + key, innerMap);
} else {
// It supposed to be a map, but we already have a value stored, which is not a map
// fall back to "." notation
processSetting(map, prefix + key + ".", rest, value);
}
}
}
}
private Object convertMapsToArrays(Map map) {
if (map.isEmpty()) {
return map;
}
boolean isArray = true;
int maxIndex = -1;
for (Map.Entry entry : map.entrySet()) {
if (isArray) {
try {
int index = Integer.parseInt(entry.getKey());
if (index >= 0) {
maxIndex = Math.max(maxIndex, index);
} else {
isArray = false;
}
} catch (NumberFormatException ex) {
isArray = false;
}
}
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map valMap = (Map) entry.getValue();
entry.setValue(convertMapsToArrays(valMap));
}
}
if (isArray && (maxIndex + 1) == map.size()) {
ArrayList newValue = new ArrayList<>(maxIndex + 1);
for (int i = 0; i <= maxIndex; i++) {
Object obj = map.get(Integer.toString(i));
if (obj == null) {
// Something went wrong. Different format?
// Bailout!
return map;
}
newValue.add(obj);
}
return newValue;
}
return map;
}
/**
* A settings that are filtered (and key is removed) with the specified prefix.
*/
public Settings getByPrefix(String prefix) {
return new Settings(
new FilteredMap(this.settings, (k) -> k.startsWith(prefix), prefix),
secureSettings == null ? null : new PrefixedSecureSettings(secureSettings, prefix, s -> s.startsWith(prefix))
);
}
/**
* Returns a new settings object that contains all setting of the current one filtered by the given settings key predicate.
*/
public Settings filter(Predicate predicate) {
return new Settings(
new FilteredMap(this.settings, predicate, null),
secureSettings == null ? null : new PrefixedSecureSettings(secureSettings, "", predicate)
);
}
/**
* Returns the settings mapped to the given setting name.
*/
public Settings getAsSettings(String setting) {
return getByPrefix(setting + ".");
}
/**
* Returns the setting value associated with the setting key.
*
* @param setting The setting key
* @return The setting value, {@code null} if it does not exists.
*/
public String get(String setting) {
return toString(settings.get(setting));
}
/**
* Returns the setting value associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public String get(String setting, String defaultValue) {
String retVal = get(setting);
return retVal == null ? defaultValue : retVal;
}
/**
* Returns the setting value (as float) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public Float getAsFloat(String setting, Float defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Float.parseFloat(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse float setting [" + setting + "] with value [" + sValue + "]", e);
}
}
/**
* Returns the setting value (as double) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public Double getAsDouble(String setting, Double defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Double.parseDouble(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse double setting [" + setting + "] with value [" + sValue + "]", e);
}
}
/**
* Returns the setting value (as int) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public Integer getAsInt(String setting, Integer defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Integer.parseInt(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse int setting [" + setting + "] with value [" + sValue + "]", e);
}
}
/**
* Returns the setting value (as long) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public Long getAsLong(String setting, Long defaultValue) {
String sValue = get(setting);
if (sValue == null) {
return defaultValue;
}
try {
return Long.parseLong(sValue);
} catch (NumberFormatException e) {
throw new SettingsException("Failed to parse long setting [" + setting + "] with value [" + sValue + "]", e);
}
}
/**
* Returns true
iff the given key has a value in this settings object
*/
public boolean hasValue(String key) {
return settings.get(key) != null;
}
/**
* We have to lazy initialize the deprecation logger as otherwise a static logger here would be constructed before logging is configured
* leading to a runtime failure (see {@link LogConfigurator#checkErrorListener()} ). The premature construction would come from any
* {@link Setting} object constructed in, for example, {@link org.opensearch.env.Environment}.
*
* @opensearch.internal
*/
static class DeprecationLoggerHolder {
static DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(Settings.class);
}
/**
* Returns the setting value (as boolean) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public Boolean getAsBoolean(String setting, Boolean defaultValue) {
return Booleans.parseBoolean(get(setting), defaultValue);
}
/**
* Returns the setting value (as time) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public TimeValue getAsTime(String setting, TimeValue defaultValue) {
return parseTimeValue(get(setting), defaultValue, setting);
}
/**
* Returns the setting value (as size) associated with the setting key. If it does not exists,
* returns the default value provided.
*/
public ByteSizeValue getAsBytesSize(String setting, ByteSizeValue defaultValue) throws SettingsException {
return parseBytesSizeValue(get(setting), defaultValue, setting);
}
/**
* Returns the setting value (as size) associated with the setting key. Provided values can either be
* absolute values (interpreted as a number of bytes), byte sizes (eg. 1mb) or percentage of the heap size
* (eg. 12%). If it does not exists, parses the default value provided.
*/
public ByteSizeValue getAsMemory(String setting, String defaultValue) throws SettingsException {
return MemorySizeValue.parseBytesSizeValueOrHeapRatio(get(setting, defaultValue), setting);
}
/**
* The values associated with a setting key as an immutable list.
*
* It will also automatically load a comma separated list under the settingPrefix and merge with
* the numbered format.
*
* @param key The setting key to load the list by
* @return The setting list values
*/
public List getAsList(String key) throws SettingsException {
return getAsList(key, Collections.emptyList());
}
/**
* The values associated with a setting key as an immutable list.
*
* If commaDelimited is true, it will automatically load a comma separated list under the settingPrefix and merge with
* the numbered format.
*
* @param key The setting key to load the list by
* @return The setting list values
*/
public List getAsList(String key, List defaultValue) throws SettingsException {
return getAsList(key, defaultValue, true);
}
/**
* The values associated with a setting key as an immutable list.
*
* It will also automatically load a comma separated list under the settingPrefix and merge with
* the numbered format.
*
* @param key The setting key to load the list by
* @param defaultValue The default value to use if no value is specified
* @param commaDelimited Whether to try to parse a string as a comma-delimited value
* @return The setting list values
*/
public List getAsList(String key, List defaultValue, Boolean commaDelimited) throws SettingsException {
List result = new ArrayList<>();
final Object valueFromPrefix = settings.get(key);
if (valueFromPrefix != null) {
if (valueFromPrefix instanceof List) {
return Collections.unmodifiableList((List) valueFromPrefix);
} else if (commaDelimited) {
String[] strings = Strings.splitStringByCommaToArray(get(key));
if (strings.length > 0) {
for (String string : strings) {
result.add(string.trim());
}
}
} else {
result.add(get(key).trim());
}
}
if (result.isEmpty()) {
return defaultValue;
}
return Collections.unmodifiableList(result);
}
/**
* Returns group settings for the given setting prefix.
*/
public Map getGroups(String settingPrefix) throws SettingsException {
return getGroups(settingPrefix, false);
}
/**
* Returns group settings for the given setting prefix.
*/
public Map getGroups(String settingPrefix, boolean ignoreNonGrouped) throws SettingsException {
if (!Strings.hasLength(settingPrefix)) {
throw new IllegalArgumentException("illegal setting prefix " + settingPrefix);
}
if (settingPrefix.charAt(settingPrefix.length() - 1) != '.') {
settingPrefix = settingPrefix + ".";
}
return getGroupsInternal(settingPrefix, ignoreNonGrouped);
}
private Map getGroupsInternal(String settingPrefix, boolean ignoreNonGrouped) throws SettingsException {
Settings prefixSettings = getByPrefix(settingPrefix);
Map groups = new HashMap<>();
for (String groupName : prefixSettings.names()) {
Settings groupSettings = prefixSettings.getByPrefix(groupName + ".");
if (groupSettings.isEmpty()) {
if (ignoreNonGrouped) {
continue;
}
throw new SettingsException(
"Failed to get setting group for ["
+ settingPrefix
+ "] setting prefix and setting ["
+ settingPrefix
+ groupName
+ "] because of a missing '.'"
);
}
groups.put(groupName, groupSettings);
}
return Collections.unmodifiableMap(groups);
}
/**
* Returns group settings for the given setting prefix.
*/
public Map getAsGroups() throws SettingsException {
return getGroupsInternal("", false);
}
/**
* Returns a parsed version.
*/
public Version getAsVersion(String setting, Version defaultVersion) throws SettingsException {
String sValue = get(setting);
if (sValue == null) {
return defaultVersion;
}
try {
return Version.fromId(Integer.parseInt(sValue));
} catch (Exception e) {
throw new SettingsException("Failed to parse version setting [" + setting + "] with value [" + sValue + "]", e);
}
}
/**
* @return The direct keys of this settings
*/
public Set names() {
synchronized (firstLevelNames) {
if (firstLevelNames.get() == null) {
Stream stream = settings.keySet().stream();
if (secureSettings != null) {
stream = Stream.concat(stream, secureSettings.getSettingNames().stream());
}
Set names = stream.map(k -> {
int i = k.indexOf('.');
if (i < 0) {
return k;
} else {
return k.substring(0, i);
}
}).collect(Collectors.toSet());
firstLevelNames.set(Collections.unmodifiableSet(names));
}
}
return firstLevelNames.get();
}
/**
* Returns the settings as delimited string.
*/
public String toDelimitedString(char delimiter) {
StringBuilder sb = new StringBuilder();
for (Map.Entry entry : settings.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append(delimiter);
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Settings that = (Settings) o;
return Objects.equals(settings, that.settings);
}
@Override
public int hashCode() {
return settings != null ? settings.hashCode() : 0;
}
public static Settings readSettingsFromStream(StreamInput in) throws IOException {
Builder builder = new Builder();
int numberOfSettings = in.readVInt();
for (int i = 0; i < numberOfSettings; i++) {
String key = in.readString();
Object value = in.readGenericValue();
if (value == null) {
builder.putNull(key);
} else if (value instanceof List) {
builder.putList(key, (List) value);
} else {
builder.put(key, value.toString());
}
}
return builder.build();
}
public static void writeSettingsToStream(Settings settings, StreamOutput out) throws IOException {
// pull settings to exclude secure settings in size()
Set> entries = settings.settings.entrySet();
out.writeVInt(entries.size());
for (Map.Entry entry : entries) {
out.writeString(entry.getKey());
out.writeGenericValue(entry.getValue());
}
}
/**
* Returns a builder to be used in order to build settings.
*/
public static Builder builder() {
return new Builder();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
Settings settings = SettingsFilter.filterSettings(params, this);
if (!params.paramAsBoolean("flat_settings", false)) {
for (Map.Entry entry : settings.getAsStructuredMap().entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
} else {
for (Map.Entry entry : settings.settings.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
}
return builder;
}
/**
* Parsers the generated xcontent from {@link Settings#toXContent(XContentBuilder, Params)} into a new Settings object.
* Note this method requires the parser to either be positioned on a null token or on
* {@link XContentParser.Token#START_OBJECT}.
*/
public static Settings fromXContent(XContentParser parser) throws IOException {
return fromXContent(parser, true, false);
}
private static Settings fromXContent(XContentParser parser, boolean allowNullValues, boolean validateEndOfStream) throws IOException {
if (parser.currentToken() == null) {
parser.nextToken();
}
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser);
Builder innerBuilder = Settings.builder();
StringBuilder currentKeyBuilder = new StringBuilder();
fromXContent(parser, currentKeyBuilder, innerBuilder, allowNullValues);
if (validateEndOfStream) {
// ensure we reached the end of the stream
XContentParser.Token lastToken = null;
try {
while (!parser.isClosed() && (lastToken = parser.nextToken()) == null)
;
} catch (Exception e) {
throw new OpenSearchParseException(
"malformed, expected end of settings but encountered additional content starting at line number: [{}], "
+ "column number: [{}]",
e,
parser.getTokenLocation().lineNumber,
parser.getTokenLocation().columnNumber
);
}
if (lastToken != null) {
throw new OpenSearchParseException(
"malformed, expected end of settings but encountered additional content starting at line number: [{}], "
+ "column number: [{}]",
parser.getTokenLocation().lineNumber,
parser.getTokenLocation().columnNumber
);
}
}
return innerBuilder.build();
}
private static void fromXContent(XContentParser parser, StringBuilder keyBuilder, Settings.Builder builder, boolean allowNullValues)
throws IOException {
final int length = keyBuilder.length();
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
if (parser.currentToken() == XContentParser.Token.FIELD_NAME) {
keyBuilder.setLength(length);
keyBuilder.append(parser.currentName());
} else if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
keyBuilder.append('.');
fromXContent(parser, keyBuilder, builder, allowNullValues);
} else if (parser.currentToken() == XContentParser.Token.START_ARRAY) {
List list = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
list.add(parser.text());
} else if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
list.add(parser.text()); // just use the string representation here
} else if (parser.currentToken() == XContentParser.Token.VALUE_BOOLEAN) {
list.add(String.valueOf(parser.text()));
} else {
throw new IllegalStateException("only value lists are allowed in serialized settings");
}
}
String key = keyBuilder.toString();
validateValue(key, list, parser, allowNullValues);
builder.putList(key, list);
} else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
String key = keyBuilder.toString();
validateValue(key, null, parser, allowNullValues);
builder.putNull(key);
} else if (parser.currentToken() == XContentParser.Token.VALUE_STRING
|| parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
String key = keyBuilder.toString();
String value = parser.text();
validateValue(key, value, parser, allowNullValues);
builder.put(key, value);
} else if (parser.currentToken() == XContentParser.Token.VALUE_BOOLEAN) {
String key = keyBuilder.toString();
validateValue(key, parser.text(), parser, allowNullValues);
builder.put(key, parser.booleanValue());
} else {
XContentParserUtils.throwUnknownToken(parser.currentToken(), parser.getTokenLocation());
}
}
}
private static void validateValue(String key, Object currentValue, XContentParser parser, boolean allowNullValues) {
if (currentValue == null && allowNullValues == false) {
throw new OpenSearchParseException(
"null-valued setting found for key [{}] found at line number [{}], column number [{}]",
key,
parser.getTokenLocation().lineNumber,
parser.getTokenLocation().columnNumber
);
}
}
public static final Set FORMAT_PARAMS = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("settings_filter", "flat_settings"))
);
/**
* Returns {@code true} if this settings object contains no settings
* @return {@code true} if this settings object contains no settings
*/
public boolean isEmpty() {
return this.settings.isEmpty() && (secureSettings == null || secureSettings.getSettingNames().isEmpty());
}
/** Returns the number of settings in this settings object. */
public int size() {
return keySet().size();
}
/** Returns the fully qualified setting names contained in this settings object. */
public Set keySet() {
if (keys.get() == null) {
synchronized (keys) {
// Check that the keys are still null now that we have acquired the lock
if (keys.get() == null) {
if (secureSettings == null) {
keys.set(settings.keySet());
} else {
Stream stream = Stream.concat(settings.keySet().stream(), secureSettings.getSettingNames().stream());
// uniquify, since for legacy reasons the same setting name may exist in both
keys.set(Collections.unmodifiableSet(stream.collect(Collectors.toSet())));
}
}
}
}
return keys.get();
}
/**
* A builder allowing to put different settings and then {@link #build()} an immutable
* settings implementation. Use {@link Settings#builder()} in order to
* construct it.
*
* @opensearch.api
*/
@PublicApi(since = "1.0.0")
public static class Builder {
public static final Settings EMPTY_SETTINGS = Settings.EMPTY;
// we use a sorted map for consistent serialization when using getAsMap()
private final Map map = new TreeMap<>();
private final SetOnce secureSettings = new SetOnce<>();
private Builder() {
}
public Set keys() {
return this.map.keySet();
}
/**
* Removes the provided setting from the internal map holding the current list of settings.
*/
public String remove(String key) {
return Settings.toString(map.remove(key));
}
/**
* Returns a setting value based on the setting key.
*/
public String get(String key) {
return Settings.toString(map.get(key));
}
/** Return the current secure settings, or {@code null} if none have been set. */
public SecureSettings getSecureSettings() {
return secureSettings.get();
}
public Builder setSecureSettings(SecureSettings secureSettings) {
if (secureSettings.isLoaded() == false) {
throw new IllegalStateException("Secure settings must already be loaded");
}
if (this.secureSettings.get() != null) {
throw new IllegalArgumentException(
"Secure settings already set. Existing settings: "
+ this.secureSettings.get().getSettingNames()
+ ", new settings: "
+ secureSettings.getSettingNames()
);
}
this.secureSettings.set(secureSettings);
return this;
}
/**
* Sets a path setting with the provided setting key and path.
*
* @param key The setting key
* @param path The setting path
* @return The builder
*/
public Builder put(String key, Path path) {
return put(key, path.toString());
}
/**
* Sets a time value setting with the provided setting key and value.
*
* @param key The setting key
* @param timeValue The setting timeValue
* @return The builder
*/
public Builder put(final String key, final TimeValue timeValue) {
return put(key, timeValue.getStringRep());
}
/**
* Sets a byteSizeValue setting with the provided setting key and byteSizeValue.
*
* @param key The setting key
* @param byteSizeValue The setting value
* @return The builder
*/
public Builder put(final String key, final ByteSizeValue byteSizeValue) {
return put(key, byteSizeValue.getStringRep());
}
/**
* Sets an enum setting with the provided setting key and enum instance.
*
* @param key The setting key
* @param enumValue The setting value
* @return The builder
*/
public Builder put(String key, Enum> enumValue) {
return put(key, enumValue.toString());
}
/**
* Sets an level setting with the provided setting key and level instance.
*
* @param key The setting key
* @param level The setting value
* @return The builder
*/
public Builder put(String key, Level level) {
return put(key, level.toString());
}
/**
* Sets an lucene version setting with the provided setting key and lucene version instance.
*
* @param key The setting key
* @param luceneVersion The setting value
* @return The builder
*/
public Builder put(String key, org.apache.lucene.util.Version luceneVersion) {
return put(key, luceneVersion.toString());
}
/**
* Sets a setting with the provided setting key and value.
*
* @param key The setting key
* @param value The setting value
* @return The builder
*/
public Builder put(String key, String value) {
map.put(key, value);
return this;
}
public Builder copy(String key, Settings source) {
return copy(key, key, source);
}
public Builder copy(String key, String sourceKey, Settings source) {
if (source.settings.containsKey(sourceKey) == false) {
throw new IllegalArgumentException("source key not found in the source settings");
}
final Object value = source.settings.get(sourceKey);
if (value instanceof List) {
return putList(key, (List) value);
} else if (value == null) {
return putNull(key);
} else {
return put(key, Settings.toString(value));
}
}
/**
* Sets a null value for the given setting key
*/
public Builder putNull(String key) {
return put(key, (String) null);
}
/**
* Sets the setting with the provided setting key and the boolean value.
*
* @param setting The setting key
* @param value The boolean value
* @return The builder
*/
public Builder put(String setting, boolean value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the int value.
*
* @param setting The setting key
* @param value The int value
* @return The builder
*/
public Builder put(String setting, int value) {
put(setting, String.valueOf(value));
return this;
}
public Builder put(String setting, Version version) {
put(setting, version.id);
return this;
}
/**
* Sets the setting with the provided setting key and the long value.
*
* @param setting The setting key
* @param value The long value
* @return The builder
*/
public Builder put(String setting, long value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the float value.
*
* @param setting The setting key
* @param value The float value
* @return The builder
*/
public Builder put(String setting, float value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the double value.
*
* @param setting The setting key
* @param value The double value
* @return The builder
*/
public Builder put(String setting, double value) {
put(setting, String.valueOf(value));
return this;
}
/**
* Sets the setting with the provided setting key and the time value.
*
* @param setting The setting key
* @param value The time value
* @return The builder
*/
public Builder put(final String setting, final long value, final TimeUnit timeUnit) {
put(setting, new TimeValue(value, timeUnit));
return this;
}
/**
* Sets the setting with the provided setting key and the size value.
*
* @param setting The setting key
* @param value The size value
* @return The builder
*/
public Builder put(String setting, long value, ByteSizeUnit sizeUnit) {
put(setting, sizeUnit.toBytes(value) + "b");
return this;
}
/**
* Sets the setting with the provided setting key and an array of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putList(String setting, String... values) {
return putList(setting, Arrays.asList(values));
}
/**
* Sets the setting with the provided setting key and a list of values.
*
* @param setting The setting key
* @param values The values
* @return The builder
*/
public Builder putList(String setting, List values) {
remove(setting);
map.put(setting, new ArrayList<>(values));
return this;
}
/**
* Sets all the provided settings including secure settings
*/
public Builder put(Settings settings) {
return put(settings, true);
}
/**
* Sets all the provided settings.
* @param settings the settings to set
* @param copySecureSettings if true
all settings including secure settings are copied.
*/
public Builder put(Settings settings, boolean copySecureSettings) {
Map settingsMap = new HashMap<>(settings.settings);
processLegacyLists(settingsMap);
map.putAll(settingsMap);
if (copySecureSettings && settings.getSecureSettings() != null) {
setSecureSettings(settings.getSecureSettings());
}
return this;
}
private void processLegacyLists(Map map) {
String[] array = map.keySet().toArray(new String[map.size()]);
for (String key : array) {
if (key.endsWith(".0")) { // let's only look at the head of the list and convert in order starting there.
int counter = 0;
String prefix = key.substring(0, key.lastIndexOf('.'));
if (map.containsKey(prefix)) {
throw new IllegalStateException(
"settings builder can't contain values for ["
+ prefix
+ "="
+ map.get(prefix)
+ "] and ["
+ key
+ "="
+ map.get(key)
+ "]"
);
}
List values = new ArrayList<>();
while (true) {
String listKey = prefix + '.' + (counter++);
String value = get(listKey);
if (value == null) {
map.put(prefix, values);
break;
} else {
values.add(value);
map.remove(listKey);
}
}
}
}
}
/**
* Loads settings from a map.
*/
public Builder loadFromMap(Map map) {
// TODO: do this without a serialization round-trip
try (XContentBuilder builder = MediaTypeRegistry.contentBuilder(MediaTypeRegistry.JSON)) {
builder.map(map);
return loadFromSource(builder.toString(), builder.contentType());
} catch (IOException e) {
throw new OpenSearchGenerationException("Failed to generate [" + map + "]", e);
}
}
/**
* Loads settings from the actual string content that represents them using {@link #fromXContent(XContentParser)}
*/
public Builder loadFromSource(String source, MediaType mediaType) {
try (
XContentParser parser = mediaType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, source)
) {
this.put(fromXContent(parser, true, true));
} catch (Exception e) {
throw new SettingsException("Failed to load settings from [" + source + "]", e);
}
return this;
}
/**
* Loads settings from a url that represents them using {@link #fromXContent(XContentParser)}
* Note: Loading from a path doesn't allow null
values in the incoming xcontent
*/
public Builder loadFromPath(Path path) throws IOException {
// NOTE: loadFromStream will close the input stream
return loadFromStream(path.getFileName().toString(), Files.newInputStream(path), false);
}
/**
* Loads settings from a stream that represents them using {@link #fromXContent(XContentParser)}
*/
public Builder loadFromStream(String resourceName, InputStream is, boolean acceptNullValues) throws IOException {
final MediaType mediaType;
if (resourceName.endsWith(".json")) {
mediaType = MediaTypeRegistry.JSON;
} else if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {
mediaType = XContentType.YAML;
} else {
throw new IllegalArgumentException("unable to detect content type from resource name [" + resourceName + "]");
}
// fromXContent doesn't use named xcontent or deprecation.
try (
XContentParser parser = mediaType.xContent()
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, is)
) {
if (parser.currentToken() == null) {
if (parser.nextToken() == null) {
return this; // empty file
}
}
put(fromXContent(parser, acceptNullValues, true));
} catch (OpenSearchParseException e) {
throw e;
} catch (Exception e) {
throw new SettingsException("Failed to load settings from [" + resourceName + "]", e);
} finally {
IOUtils.close(is);
}
return this;
}
public Builder putProperties(final Map esSettings, final Function keyFunction) {
for (final Map.Entry esSetting : esSettings.entrySet()) {
final String key = esSetting.getKey();
put(keyFunction.apply(key), esSetting.getValue());
}
return this;
}
/**
* Runs across all the settings set on this builder and
* replaces {@code ${...}} elements in each setting with
* another setting already set on this builder.
*/
public Builder replacePropertyPlaceholders() {
return replacePropertyPlaceholders(System::getenv);
}
// visible for testing
Builder replacePropertyPlaceholders(Function getenv) {
PropertyPlaceholder propertyPlaceholder = new PropertyPlaceholder("${", "}", false);
PropertyPlaceholder.PlaceholderResolver placeholderResolver = new PropertyPlaceholder.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
final String value = getenv.apply(placeholderName);
if (value != null) {
return value;
}
return Settings.toString(map.get(placeholderName));
}
@Override
public boolean shouldIgnoreMissing(String placeholderName) {
return placeholderName.startsWith("prompt.");
}
@Override
public boolean shouldRemoveMissingPlaceholder(String placeholderName) {
return !placeholderName.startsWith("prompt.");
}
};
Iterator> entryItr = map.entrySet().iterator();
while (entryItr.hasNext()) {
Map.Entry entry = entryItr.next();
if (entry.getValue() == null) {
// a null value obviously can't be replaced
continue;
}
if (entry.getValue() instanceof List) {
final ListIterator li = ((List) entry.getValue()).listIterator();
while (li.hasNext()) {
final String settingValueRaw = li.next();
final String settingValueResolved = propertyPlaceholder.replacePlaceholders(settingValueRaw, placeholderResolver);
li.set(settingValueResolved);
}
continue;
}
String value = propertyPlaceholder.replacePlaceholders(Settings.toString(entry.getValue()), placeholderResolver);
// if the values exists and has length, we should maintain it in the map
// otherwise, the replace process resolved into removing it
if (Strings.hasLength(value)) {
entry.setValue(value);
} else {
entryItr.remove();
}
}
return this;
}
/**
* Checks that all settings(except archived settings and wildcards) in the builder start with the specified prefix.
*
* If a setting doesn't start with the prefix, the builder appends the prefix to such setting.
*/
public Builder normalizePrefix(String prefix) {
Map replacements = new HashMap<>();
Iterator> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
String key = entry.getKey();
if (key.startsWith(prefix) == false && key.endsWith("*") == false && key.startsWith(ARCHIVED_SETTINGS_PREFIX) == false) {
replacements.put(prefix + key, entry.getValue());
iterator.remove();
}
}
map.putAll(replacements);
return this;
}
/**
* Builds a {@link Settings} (underlying uses {@link Settings}) based on everything
* set on this builder.
*/
public Settings build() {
processLegacyLists(map);
return new Settings(map, secureSettings.get());
}
}
// TODO We could use an FST internally to make things even faster and more compact
private static final class FilteredMap extends AbstractMap {
private final Map delegate;
private final Predicate filter;
private final String prefix;
// we cache that size since we have to iterate the entire set
// this is safe to do since this map is only used with unmodifiable maps
private int size = -1;
@Override
public Set> entrySet() {
Set> delegateSet = delegate.entrySet();
AbstractSet> filterSet = new AbstractSet>() {
@Override
public Iterator> iterator() {
Iterator> iter = delegateSet.iterator();
return new Iterator>() {
private int numIterated;
private Entry currentElement;
@Override
public boolean hasNext() {
if (currentElement != null) {
return true; // protect against calling hasNext twice
} else {
if (numIterated == size) { // early terminate
assert size != -1 : "size was never set: " + numIterated + " vs. " + size;
return false;
}
while (iter.hasNext()) {
if (filter.test((currentElement = iter.next()).getKey())) {
numIterated++;
return true;
}
}
// we didn't find anything
currentElement = null;
return false;
}
}
@Override
public Entry next() {
if (currentElement == null && hasNext() == false) { // protect against no #hasNext call or not respecting it
throw new NoSuchElementException("make sure to call hasNext first");
}
final Entry current = this.currentElement;
this.currentElement = null;
if (prefix == null) {
return current;
}
return new Entry() {
@Override
public String getKey() {
return current.getKey().substring(prefix.length());
}
@Override
public Object getValue() {
return current.getValue();
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
};
}
};
}
@Override
public int size() {
return FilteredMap.this.size();
}
};
return filterSet;
}
private FilteredMap(Map delegate, Predicate filter, String prefix) {
this.delegate = delegate;
this.filter = filter;
this.prefix = prefix;
}
@Override
public Object get(Object key) {
if (key instanceof String) {
final String theKey = prefix == null ? (String) key : prefix + key;
if (filter.test(theKey)) {
return delegate.get(theKey);
}
}
return null;
}
@Override
public boolean containsKey(Object key) {
if (key instanceof String) {
final String theKey = prefix == null ? (String) key : prefix + key;
if (filter.test(theKey)) {
return delegate.containsKey(theKey);
}
}
return false;
}
@Override
public int size() {
if (size == -1) {
size = Math.toIntExact(delegate.keySet().stream().filter(filter).count());
}
return size;
}
}
/**
* Prefixed secure settings
*
* @opensearch.internal
*/
private static class PrefixedSecureSettings implements SecureSettings {
private final SecureSettings delegate;
private final UnaryOperator addPrefix;
private final UnaryOperator removePrefix;
private final Predicate keyPredicate;
private final SetOnce> settingNames = new SetOnce<>();
PrefixedSecureSettings(SecureSettings delegate, String prefix, Predicate keyPredicate) {
this.delegate = delegate;
this.addPrefix = s -> prefix + s;
this.removePrefix = s -> s.substring(prefix.length());
this.keyPredicate = keyPredicate;
}
@Override
public boolean isLoaded() {
return delegate.isLoaded();
}
@Override
public Set getSettingNames() {
synchronized (settingNames) {
if (settingNames.get() == null) {
Set names = delegate.getSettingNames()
.stream()
.filter(keyPredicate)
.map(removePrefix)
.collect(Collectors.toSet());
settingNames.set(Collections.unmodifiableSet(names));
}
}
return settingNames.get();
}
@Override
public SecureString getString(String setting) throws GeneralSecurityException {
return delegate.getString(addPrefix.apply(setting));
}
@Override
public InputStream getFile(String setting) throws GeneralSecurityException {
return delegate.getFile(addPrefix.apply(setting));
}
@Override
public byte[] getSHA256Digest(String setting) throws GeneralSecurityException {
return delegate.getSHA256Digest(addPrefix.apply(setting));
}
@Override
public void close() throws IOException {
delegate.close();
}
}
@Override
public String toString() {
try (XContentBuilder builder = MediaTypeRegistry.JSON.contentBuilder()) {
builder.startObject();
toXContent(builder, new MapParams(Collections.singletonMap("flat_settings", "true")));
builder.endObject();
return builder.toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static String toString(Object o) {
return o == null ? null : o.toString();
}
}