sirius.kernel.settings.Settings Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.settings;
import com.google.common.collect.Lists;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueFactory;
import com.typesafe.config.ConfigValueType;
import sirius.kernel.commons.Context;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Strings;
import sirius.kernel.commons.Value;
import sirius.kernel.di.std.Priorized;
import sirius.kernel.health.Exceptions;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Provides a wrapper around a {@link Config} supplied by typesafe config.
*
* Contains various boilerplate methods to safe- and quickly access the underlying config.
*/
public class Settings {
private static final String PRIORITY = "priority";
private static final String ID = "id";
private final Config config;
/**
* Creates a new wrapper for the given config.
*
* @param config the config to wrap
*/
public Settings(@Nonnull Config config) {
this.config = config;
}
/**
* Provides access to the underlying config object.
*
* @return the underlying config object
*/
public Config getConfig() {
return config;
}
/**
* Returns the {@link Value} defined for the given key.
*
* If this extension doesn't provide a value for this key, but there is an extension with the name
* default which provides a value, this is used.
*
* If the value in the config file starts with a dollar sign, the value is treated as an i18n key and the
* returned value will contain the translation for the current language.
*
* @param path the access path to retrieve the value
* @return the value wrapping the contents for the given path. This will never by null,
* but might be empty: {@link Value#isNull()}
*/
@Nonnull
public Value get(String path) {
try {
return Value.of(getConfig().getAnyRef(path));
} catch (ConfigException e) {
Exceptions.handle(e);
return Value.EMPTY;
}
}
/**
* Returns all values defined in this extension as {@link Context}.
*
* @return a context containing all values defined by this extension or the default extension.
*/
@Nonnull
public Context getContext() {
Context ctx = Context.create();
for (Map.Entry entry : config.entrySet()) {
ctx.put(entry.getKey(), get(entry.getKey()).get());
}
return ctx;
}
/**
* Returns the sub config available for the given key.
*
* @param key name of the sub config to retrieve
* @return the sub config for the given key or null if no such config exists.
*/
@Nullable
public Config getConfig(String key) {
return getConfig().getConfig(key);
}
/**
* Returns all config objects underneath the given key.
*
* Assume we have the following config:
*
* test {
* sub {
* a { ... }
* b { ... }
* }
* }
*
*
* Then getConfigs("sub") for the extension "test" would return a list containing a and b wrapped as config.
*
* The name of the config object is available as "id".
*
* The list will be sorted by "priority". If no explicit priority is given, we try to sort elements along their
* natural order within the file. If multiple files are merged together, the behaviour of this approach is
* undefined and priority should be used.
*
* @param key the path to the config object containing a list of sub object.
* @return a list of config objects underneath the given object or an empty list if there are none
*/
@Nonnull
public List extends Config> getConfigs(String key) {
List result = Lists.newArrayList();
Config cfg = getConfig(key);
if (cfg != null) {
for (Map.Entry e : cfg.root().entrySet()) {
if (e.getValue().valueType() == ConfigValueType.OBJECT) {
Config subCfg = ((ConfigObject) e.getValue()).toConfig();
result.add(subCfg.withValue(ID, ConfigValueFactory.fromAnyRef(e.getKey())));
}
}
}
result.sort((a, b) -> {
int prioA = a.hasPath(PRIORITY) ? a.getInt(PRIORITY) : Priorized.DEFAULT_PRIORITY;
int prioB = b.hasPath(PRIORITY) ? b.getInt(PRIORITY) : Priorized.DEFAULT_PRIORITY;
if (prioA == prioB) {
prioA = a.origin().lineNumber();
prioB = b.origin().lineNumber();
}
return prioA - prioB;
});
return result;
}
/**
* Returns the duration in milliseconds defined for the given key.
*
* If this extension doesn't provide a value for this key, but there is an extension with the name
* default which provides a value, this is used.
*
* @param path the access path to retrieve the value
* @return the encoded duration as milliseconds.
* @throws sirius.kernel.health.HandledException if an invalid value was given in the config
*/
public long getMilliseconds(String path) {
try {
return config.getDuration(path, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw Exceptions.handle(e);
}
}
/**
* Returns the string for the given key.
*
* @param key the key used to lookup the string value
* @return the string value stored of the given key
*/
public String getString(String key) {
return get(key).asString();
}
/**
* Returns the integer value for the given key.
*
* @param key the key used to lookup the value
* @return the integer value stored in the config or 0 if an invalid value is present
*/
public int getInt(String key) {
return get(key).asInt(0);
}
/**
* Returns the list of strings for the given key.
*
* @param key the key used to lookup the value
* @return a list of strings stored of the given key
*/
public List getStringList(String key) {
return getConfig().getStringList(key);
}
/**
* Reads a embedded map.
*
* @param key the key of the map to read
* @return the map within the config
*/
public Map getMap(String key) {
Map result = new HashMap<>();
config.getConfig(key)
.entrySet()
.forEach(e -> result.put(e.getKey(), Value.of(e.getValue().unwrapped()).asString()));
return result;
}
/**
* Injects the value selected by 'key' out of the given config into the given field of the given target.
*
* @param target the target object to populate
* @param field the field to fill
* @param key the key to read
* @return true if a value was present and injected, false otherwise
*/
public boolean injectValueFromConfig(Object target, Field field, String key) {
if (!config.hasPath(key)) {
return false;
}
field.setAccessible(true);
try {
injectIntoField(target, field, key);
return true;
} catch (IllegalAccessException e) {
// This should not happen, as we set the field to be accessible
throw new IllegalArgumentException(Strings.apply("Cannot fill field '%s.%s' of type %s with a config value!",
field.getDeclaringClass().getName(),
field.getName(),
field.getType().getName()), e);
}
}
@SuppressWarnings({"unchecked", "rawtypes", "squid:S3776", "squid:MethodCyclomaticComplexity"})
@Explain("This is the shortest and most efficient way to check all those types.")
private void injectIntoField(Object target, Field field, String key) throws IllegalAccessException {
if (String.class.equals(field.getType())) {
field.set(target, config.getString(key));
} else if (int.class.equals(field.getType()) || Integer.class.equals(field.getType())) {
field.set(target, config.getInt(key));
} else if (long.class.equals(field.getType()) || Long.class.equals(field.getType())) {
if (config.getValue(key).valueType() == ConfigValueType.NUMBER) {
field.set(target, config.getLong(key));
} else {
field.set(target, config.getBytes(key));
}
} else if (boolean.class.equals(field.getType()) || Boolean.class.equals(field.getType())) {
field.set(target, config.getBoolean(key));
} else if (List.class.equals(field.getType())) {
field.set(target, config.getStringList(key));
} else if (Map.class.equals(field.getType())) {
field.set(target, getMap(key));
} else if (Duration.class.equals(field.getType())) {
field.set(target, Duration.ofMillis(config.getDuration(key, TimeUnit.MILLISECONDS)));
} else if (field.getType().isEnum()) {
field.set(target, Value.of(config.getString(key)).asEnum((Class extends Enum>) field.getType()));
} else if (Set.class.equals(field.getType())) {
field.set(target, new HashSet<>(config.getStringList(key)));
} else if (float.class.equals(field.getType()) || Float.class.equals(field.getType())) {
field.set(target, config.getNumber(key).floatValue());
} else if (double.class.equals(field.getType()) || Double.class.equals(field.getType())) {
field.set(target, config.getNumber(key).doubleValue());
} else {
throw new IllegalArgumentException(Strings.apply("Cannot fill field '%s.%s' of type %s with a config value!",
field.getDeclaringClass().getName(),
field.getName(),
field.getType().getName()));
}
}
}