fix list/array settings

This commit is contained in:
Simon Willnauer 2015-12-16 11:52:30 +01:00
parent d0dbfce49a
commit 9cf2f42d15
5 changed files with 122 additions and 18 deletions

View File

@ -84,7 +84,6 @@ public class Booleans {
* throws exception if string cannot be parsed to boolean * throws exception if string cannot be parsed to boolean
*/ */
public static Boolean parseBooleanExact(String value) { public static Boolean parseBooleanExact(String value) {
boolean isFalse = isExplicitFalse(value); boolean isFalse = isExplicitFalse(value);
if (isFalse) { if (isFalse) {
return false; return false;
@ -94,7 +93,7 @@ public class Booleans {
return true; return true;
} }
throw new IllegalArgumentException("value cannot be parsed to boolean [ true/1/on/yes OR false/0/off/no ] "); throw new IllegalArgumentException("Failed to parse value [" + value + "] cannot be parsed to boolean [ true/1/on/yes OR false/0/off/no ]");
} }
public static Boolean parseBoolean(String value, Boolean defaultValue) { public static Boolean parseBoolean(String value, Boolean defaultValue) {

View File

@ -18,9 +18,11 @@
*/ */
package org.elasticsearch.common.settings; package org.elasticsearch.common.settings;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.support.ToXContentToBytes; import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.regex.Regex;
@ -30,6 +32,8 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.*; import org.elasticsearch.common.xcontent.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -38,7 +42,7 @@ import java.util.function.Function;
*/ */
public class Setting<T> extends ToXContentToBytes { public class Setting<T> extends ToXContentToBytes {
private final String key; private final String key;
private final Function<Settings, String> defaultValue; protected final Function<Settings, String> defaultValue;
private final Function<String, T> parser; private final Function<String, T> parser;
private final boolean dynamic; private final boolean dynamic;
private final Scope scope; private final Scope scope;
@ -127,7 +131,7 @@ public class Setting<T> extends ToXContentToBytes {
* Returns the raw (string) settings value. If the setting is not present in the given settings object the default value is returned * 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. * instead. This is useful if the value can't be parsed due to an invalid value to access the actual value.
*/ */
public final String getRaw(Settings settings) { public String getRaw(Settings settings) {
return settings.get(key, defaultValue.apply(settings)); return settings.get(key, defaultValue.apply(settings));
} }
@ -300,6 +304,54 @@ public class Setting<T> extends ToXContentToBytes {
return timeSetting(key, defaultValue, TimeValue.timeValueMillis(0), dynamic, scope); return timeSetting(key, defaultValue, TimeValue.timeValueMillis(0), dynamic, scope);
} }
public static <T> Setting<List<T>> listSetting(String key, List<String> defaultStringValue, Function<String, T> singleValueParser, boolean dynamic, Scope scope) {
Function<String, List<T>> parser = (s) -> {
try (XContentParser xContentParser = XContentType.JSON.xContent().createParser(s)){
XContentParser.Token token = xContentParser.nextToken();
if (token != XContentParser.Token.START_ARRAY) {
throw new IllegalArgumentException("expected START_ARRAY but got " + token);
}
ArrayList<T> 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(singleValueParser.apply(xContentParser.text()));
}
return list;
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse array", e);
}
};
return new Setting<List<T>>(key, arrayToParsableString(defaultStringValue.toArray(Strings.EMPTY_ARRAY)), parser, dynamic, scope) {
@Override
public String getRaw(Settings settings) {
String[] array = settings.getAsArray(key, null);
return array == null ? defaultValue.apply(settings) : arrayToParsableString(array);
}
};
}
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<Settings> groupSetting(String key, boolean dynamic, Scope scope) { public static Setting<Settings> groupSetting(String key, boolean dynamic, Scope scope) {
if (key.endsWith(".") == false) { if (key.endsWith(".") == false) {
throw new IllegalArgumentException("key must end with a '.'"); throw new IllegalArgumentException("key must end with a '.'");

View File

@ -597,6 +597,8 @@ public final class Settings implements ToXContent {
return result.toArray(new String[result.size()]); return result.toArray(new String[result.size()]);
} }
/** /**
* Returns group settings for the given setting prefix. * Returns group settings for the given setting prefix.
*/ */

View File

@ -42,10 +42,7 @@ import org.elasticsearch.common.util.concurrent.FutureUtils;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -87,8 +84,8 @@ public class TransportService extends AbstractLifecycleComponent<TransportServic
// tracer log // tracer log
public static final Setting<String[]> TRACE_LOG_INCLUDE_SETTING = new Setting<>("transport.tracer.include", "", Strings::splitStringByCommaToArray , true, Setting.Scope.CLUSTER); public static final Setting<List<String>> TRACE_LOG_INCLUDE_SETTING = Setting.listSetting("transport.tracer.include", Collections.emptyList(), (s) -> s, true, Setting.Scope.CLUSTER);
public static final Setting<String[]> TRACE_LOG_EXCLUDE_SETTING = new Setting<>("transport.tracer.exclude", "internal:discovery/zen/fd*," + TransportLivenessAction.NAME, Strings::splitStringByCommaToArray , true, Setting.Scope.CLUSTER);; public static final Setting<List<String>> TRACE_LOG_EXCLUDE_SETTING = Setting.listSetting("transport.tracer.exclude", Arrays.asList("internal:discovery/zen/fd*", TransportLivenessAction.NAME), (s) -> s, true, Setting.Scope.CLUSTER);
private final ESLogger tracerLog; private final ESLogger tracerLog;
@ -107,8 +104,8 @@ public class TransportService extends AbstractLifecycleComponent<TransportServic
super(settings); super(settings);
this.transport = transport; this.transport = transport;
this.threadPool = threadPool; this.threadPool = threadPool;
this.tracerLogInclude = TRACE_LOG_INCLUDE_SETTING.get(settings); setTracerLogInclude(TRACE_LOG_INCLUDE_SETTING.get(settings));
this.tracelLogExclude = TRACE_LOG_EXCLUDE_SETTING.get(settings); setTracerLogExclude(TRACE_LOG_EXCLUDE_SETTING.get(settings));
tracerLog = Loggers.getLogger(logger, ".tracer"); tracerLog = Loggers.getLogger(logger, ".tracer");
adapter = createAdapter(); adapter = createAdapter();
} }
@ -134,15 +131,15 @@ public class TransportService extends AbstractLifecycleComponent<TransportServic
@Inject(optional = true) @Inject(optional = true)
public void setDynamicSettings(ClusterSettings clusterSettings) { public void setDynamicSettings(ClusterSettings clusterSettings) {
clusterSettings.addSettingsUpdateConsumer(TRACE_LOG_INCLUDE_SETTING, this::setTracerLogInclude); clusterSettings.addSettingsUpdateConsumer(TRACE_LOG_INCLUDE_SETTING, this::setTracerLogInclude);
clusterSettings.addSettingsUpdateConsumer(TRACE_LOG_EXCLUDE_SETTING, this::setTracelLogExclude); clusterSettings.addSettingsUpdateConsumer(TRACE_LOG_EXCLUDE_SETTING, this::setTracerLogExclude);
} }
void setTracerLogInclude(String[] tracerLogInclude) { void setTracerLogInclude(List<String> tracerLogInclude) {
this.tracerLogInclude = tracerLogInclude; this.tracerLogInclude = tracerLogInclude.toArray(Strings.EMPTY_ARRAY);
} }
void setTracelLogExclude(String[] tracelLogExclude) { void setTracerLogExclude(List<String> tracelLogExclude) {
this.tracelLogExclude = tracelLogExclude; this.tracelLogExclude = tracelLogExclude.toArray(Strings.EMPTY_ARRAY);
} }
@Override @Override
protected void doStart() { protected void doStart() {

View File

@ -24,6 +24,9 @@ import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -72,7 +75,7 @@ public class SettingTests extends ESTestCase {
settingUpdater.apply(build, Settings.EMPTY); settingUpdater.apply(build, Settings.EMPTY);
fail("not a boolean"); fail("not a boolean");
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
assertEquals("Failed to parse value [I am not a boolean] for setting [foo.bar]", ex.getMessage()); assertEquals("Failed to parse value [I am not a boolean] cannot be parsed to boolean [ true/1/on/yes OR false/0/off/no ]", ex.getMessage());
} }
} }
@ -248,4 +251,55 @@ public class SettingTests extends ESTestCase {
assertEquals(1, c.b.intValue()); assertEquals(1, c.b.intValue());
} }
public void testListSettings() {
Setting<List<String>> listSetting = Setting.listSetting("foo.bar", Arrays.asList("foo,bar"), (s) -> s.toString(), true, Setting.Scope.CLUSTER);
List<String> value = listSetting.get(Settings.EMPTY);
assertEquals(1, value.size());
assertEquals("foo,bar", value.get(0));
List<String> input = Arrays.asList("test", "test1, test2", "test", ",,,,");
Settings.Builder builder = Settings.builder().putArray("foo.bar", input.toArray(new String[0]));
value = listSetting.get(builder.build());
assertEquals(input.size(), value.size());
assertArrayEquals(value.toArray(new String[0]), input.toArray(new String[0]));
// try to parse this really annoying format
builder = Settings.builder();
for (int i = 0; i < input.size(); i++) {
builder.put("foo.bar." + i, input.get(i));
}
value = listSetting.get(builder.build());
assertEquals(input.size(), value.size());
assertArrayEquals(value.toArray(new String[0]), input.toArray(new String[0]));
AtomicReference<List<String>> ref = new AtomicReference<>();
AbstractScopedSettings.SettingUpdater settingUpdater = listSetting.newUpdater(ref::set, logger);
assertTrue(settingUpdater.hasChanged(builder.build(), Settings.EMPTY));
settingUpdater.apply(builder.build(), Settings.EMPTY);
assertEquals(input.size(), ref.get().size());
assertArrayEquals(ref.get().toArray(new String[0]), input.toArray(new String[0]));
settingUpdater.apply(Settings.builder().putArray("foo.bar", "123").build(), builder.build());
assertEquals(1, ref.get().size());
assertArrayEquals(ref.get().toArray(new String[0]), new String[] {"123"});
settingUpdater.apply(Settings.builder().put("foo.bar", "1,2,3").build(), Settings.builder().putArray("foo.bar", "123").build());
assertEquals(3, ref.get().size());
assertArrayEquals(ref.get().toArray(new String[0]), new String[] {"1", "2", "3"});
settingUpdater.apply(Settings.EMPTY, Settings.builder().put("foo.bar", "1,2,3").build());
assertEquals(1, ref.get().size());
assertEquals("foo,bar", ref.get().get(0));
Setting<List<Integer>> otherSettings = Setting.listSetting("foo.bar", Collections.emptyList(), Integer::parseInt, true, Setting.Scope.CLUSTER);
List<Integer> defaultValue = otherSettings.get(Settings.EMPTY);
assertEquals(0, defaultValue.size());
List<Integer> intValues = otherSettings.get(Settings.builder().put("foo.bar", "0,1,2,3").build());
assertEquals(4, intValues.size());
for (int i = 0; i < intValues.size(); i++) {
assertEquals(i, intValues.get(i).intValue());
}
}
} }