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.rapidoid.config.ConfigImpl Maven / Gradle / Ivy
/*-
* #%L
* rapidoid-commons
* %%
* Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
* %%
* Licensed 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.
* #L%
*/
package org.rapidoid.config;
import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.beany.Beany;
import org.rapidoid.cls.Cls;
import org.rapidoid.collection.Coll;
import org.rapidoid.commons.Arr;
import org.rapidoid.commons.RapidoidInfo;
import org.rapidoid.commons.Str;
import org.rapidoid.env.Env;
import org.rapidoid.env.RapidoidEnv;
import org.rapidoid.lambda.Mapper;
import org.rapidoid.lambda.Operation;
import org.rapidoid.log.GlobalCfg;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.value.Value;
import org.rapidoid.value.Values;
import org.rapidoid.wrap.BoolWrap;
import java.io.File;
import java.util.*;
@Authors("Nikolche Mihajlovski")
@Since("4.1.0")
public class ConfigImpl extends RapidoidThing implements Config {
private final List baseKeys;
private final ConfigImpl root;
private final ConfigBase base;
private final boolean isRoot;
public ConfigImpl() {
this(null, false);
}
public ConfigImpl(Map entries) {
this();
update(entries);
}
public ConfigImpl(String defaultFilenameBase) {
this(defaultFilenameBase, false);
}
public ConfigImpl(String defaultFilenameBase, boolean useBuiltInDefaults) {
this.base = new ConfigBase(defaultFilenameBase, useBuiltInDefaults);
this.root = this;
this.baseKeys = U.list();
this.isRoot = true;
}
private ConfigImpl(ConfigBase base, List baseKeys, ConfigImpl root) {
this.base = base;
this.root = root;
this.baseKeys = Collections.unmodifiableList(U.list(baseKeys));
this.isRoot = false;
}
@Override
public synchronized void reset() {
clear();
base.reset();
}
@Override
public synchronized void invalidate() {
clear();
base.invalidate();
}
@Override
public Value entry(String key) {
return Values.wrap(new ConfigValueStore(this, key));
}
private List keyChain(Iterator keys) {
List keyChain = U.list(this.baseKeys);
while (keys.hasNext()) {
String key = keys.next();
U.notNull(key, "config key");
Collections.addAll(keyChain, key.split("\\."));
}
return keyChain;
}
@Override
@SuppressWarnings("unchecked")
public Config sub(String... keys) {
U.must(U.notEmpty(keys), "Keys must be specified!");
return new ConfigImpl(base, keyChain(U.iterator(keys)), root());
}
@Override
public Config sub(List keys) {
U.must(U.notEmpty(keys), "Keys must be specified!");
return new ConfigImpl(base, keyChain(keys.iterator()), root());
}
@Override
public synchronized Object get(String key) {
makeSureIsInitialized();
Object value;
synchronized (base.properties) {
value = asMap().get(key);
}
// if it's in the config, it's already overriden by the Env. If not, check manually:
if (value == null) {
value = globalOrArgConfigByRelativeKey(key);
}
return value;
}
private Object globalOrArgConfigByRelativeKey(String relKey) {
String key = fullKey(relKey, ".");
return globalOrArgConfig(key);
}
private String fullKey(String key, String separator) {
return U.join(separator, Arr.concat(U.array(baseKeys), key));
}
@Override
public synchronized boolean has(String key) {
makeSureIsInitialized();
return get(key) != null;
}
@Override
public synchronized boolean is(String key) {
makeSureIsInitialized();
Object value = get(key);
try {
return Boolean.TRUE.equals(Cls.convert(value, Boolean.class));
} catch (Exception e) {
return false;
}
}
@Override
public synchronized Map toMap() {
makeSureIsInitialized();
return Collections.unmodifiableMap(asMap());
}
@Override
public synchronized Map toMap(Class type) {
return Coll.toBeanMap(toMap(), type);
}
private Map asMap() {
makeSureIsInitialized();
if (isRoot) {
return base.properties;
} else {
synchronized (base.properties) {
Map props = base.properties;
for (String key : baseKeys) {
Object value = props.get(key);
if (value == null) {
value = Coll.synchronizedMap();
props.put(key, value);
}
if (value instanceof Map, ?>) {
props = (Map) value;
} else {
throw U.rte("Expected a Map for configuration section '%s', but found value of type: %s",
sectionTo(key), value.getClass().getSimpleName());
}
}
return props;
}
}
}
private String sectionTo(String toKey) {
String section = "";
for (String key : baseKeys) {
if (!section.isEmpty()) {
section += ".";
}
section += key;
if (key.equals(toKey)) {
break;
}
}
return section;
}
@Override
public synchronized void clear() {
if (isRoot) {
base.properties.clear();
} else {
synchronized (base.properties) {
parent().remove(lastBaseKey());
}
}
}
private String lastBaseKey() {
return baseKeys.get(baseKeys.size() - 1);
}
@Override
public synchronized void remove(String key) {
makeSureIsInitialized();
synchronized (base.properties) {
asMap().remove(key);
}
}
@Override
public synchronized void assign(Map entries) {
makeSureIsInitialized();
synchronized (base.properties) {
clear();
update(entries);
}
}
@Override
public synchronized boolean isEmpty() {
makeSureIsInitialized();
synchronized (base.properties) {
return asMap().isEmpty() && !Env.properties().hasPrefix(U.join("_", baseKeys) + "_");
}
}
@Override
public synchronized void update(Map entries) {
makeSureIsInitialized();
update(entries, true);
}
@Override
@SuppressWarnings("unchecked")
public synchronized void update(Map entries, boolean overridenByEnv) {
makeSureIsInitialized();
synchronized (base.properties) {
for (Map.Entry e : entries.entrySet()) {
String name = e.getKey();
Object value = e.getValue();
if (value instanceof Map, ?>) {
sub(name).update((Map) value, overridenByEnv);
} else {
set(name, value, overridenByEnv);
}
}
}
}
@Override
public synchronized void set(String key, Object value) {
set(key, value, true);
}
@Override
public synchronized void set(String key, Object value, boolean overridenByEnv) {
makeSureIsInitialized();
String[] keys = key.split("\\.");
if (keys.length > 1) {
Config cfg = sub(Arr.sub(keys, 0, -1));
cfg.set(U.last(keys), value, overridenByEnv);
return;
}
if (overridenByEnv) {
value = U.or(globalOrArgConfigByRelativeKey(key), value);
}
synchronized (base.properties) {
asMap().put(key, value);
}
}
@Override
public synchronized String toString() {
makeSureIsInitialized();
synchronized (base.properties) {
return asMap().toString();
}
}
@Override
@Deprecated
public synchronized void args(List args) {
mustBeRoot();
base.initial.putAll(Msc.parseArgs(args));
}
private Object globalOrArgConfig(String key) {
return U.or(base.initial.get(key), Env.properties().get(key));
}
private void mustBeRoot() {
RapidoidEnv.touch();
U.must(isRoot, "Must be Config's root!");
}
@Override
public ConfigImpl root() {
return root;
}
@Override
public synchronized Config parent() {
if (isRoot) return null;
List parentsKeys = baseKeys.subList(0, baseKeys.size() - 1);
return parentsKeys.isEmpty() ? root : root.sub(parentsKeys);
}
@Override
public List keys() {
return baseKeys;
}
@Override
public synchronized Map toFlatMap() {
makeSureIsInitialized();
Map flatMap = U.map();
Map map = toMap();
traverseToFlat(map, U.list(keys()), flatMap);
return flatMap;
}
private static void traverseToFlat(Map map, List keys, Map flatMap) {
for (Map.Entry e : map.entrySet()) {
String key = e.getKey();
Object val = e.getValue();
if (val instanceof Map, ?>) {
Map mapVal = (Map) val;
List keys2 = U.list(keys);
keys2.add(key);
traverseToFlat(mapVal, keys2, flatMap);
} else {
flatMap.put(U.join(".", keys) + "." + key, String.valueOf(val));
}
}
}
@Override
public synchronized Properties toProperties() {
makeSureIsInitialized();
Properties props = new Properties();
props.putAll(toFlatMap());
return props;
}
@Override
public ConfigAlternatives or(BasicConfig alternative) {
return new ConfigAlternatives(this, alternative);
}
@Override
public String getFilenameBase() {
return base.getFilenameBase();
}
@Override
public synchronized Config setFilenameBase(String filenameBase) {
mustBeRoot();
if (base.setFilenameBase(filenameBase)) {
invalidate(); // clear to apply changes
}
return this;
}
@Override
public String getPath() {
return base.getPath();
}
@Override
public synchronized Config setPath(String path) {
mustBeRoot();
if (base.setPath(path)) {
invalidate(); // clear to apply changes
}
if (new File(path).isAbsolute()) {
Msc.watchForChanges(path, new Operation() {
@Override
public void execute(String filename) throws Exception {
onFileSystemChange(filename);
}
});
}
return this;
}
@Override
public synchronized void applyTo(Object target) {
makeSureIsInitialized();
Beany.update(target, toMap());
}
private void makeSureIsInitialized() {
if (base.initializing) {
return;
}
if (!base.initialized) {
synchronized (this) {
if (!base.initialized) {
base.initializing = true;
root.initialize();
base.initialized = true;
}
}
}
}
protected synchronized void initialize() {
mustBeRoot();
List loaded = U.list();
base.initial.putAll(Env.argsAsMap());
overrideByEnv();
if (useBuiltInDefaults()) {
ConfigLoaderUtil.loadBuiltInConfig(this, loaded);
}
boolean useConfigFiles = U.notEmpty(getFilenameBase());
if (useConfigFiles) {
ConfigLoaderUtil.loadConfig(this, loaded);
}
overrideByEnv();
substitutePlaceholders();
Conf.applyConfig(this);
if (!loaded.isEmpty()) {
Log.info("Loaded configuration", "namespace", getFilenameBase(), "!files", loaded);
} else {
if (useConfigFiles) {
Log.warn("Didn't find any configuration files", "path", getPath());
}
}
}
private void substitutePlaceholders() {
Map flat = toFlatMap();
Set changedKeys = U.set();
for (int i = 0; i < 1000; i++) {
if (!substitutePlaceholders(flat, changedKeys)) {
break;
}
}
for (String key : changedKeys) {
set(key, flat.get(key));
}
}
private boolean substitutePlaceholders(final Map flat, final Set changedKeys) {
final BoolWrap changed = new BoolWrap();
for (Map.Entry e : U.map(flat).entrySet()) {
final String cfgKey = e.getKey();
String val = e.getValue();
if (val.contains("${")) {
val = Str.replace(val, "\\$\\{([^\\}]+)\\}", new Mapper() {
@Override
public String map(String[] src) throws Exception {
String name = src[1];
Object value = flat.get(name);
if (value == null) {
value = GlobalCfg.get(name);
}
if (value == null) {
value = getSpecialValue(name);
}
U.must(value != null, "Cannot find configuration entry '%s' for key '%s'!", name, cfgKey);
changed.value = true;
changedKeys.add(cfgKey);
return value.toString();
}
});
flat.put(cfgKey, val);
}
}
return changed.value;
}
private Object getSpecialValue(String name) {
switch (name.toLowerCase()) {
case "system.cpus":
return Runtime.getRuntime().availableProcessors();
case "rapidoid.snapshot":
return RapidoidInfo.isSnapshot();
case "rapidoid.builton":
return RapidoidInfo.builtOn();
case "rapidoid.version":
return RapidoidInfo.version();
case "rapidoid.info":
return RapidoidInfo.nameAndInfo();
default:
return null;
}
}
private void overrideByEnv() {
base.applyInitialConfig(this);
}
@Override
public synchronized boolean useBuiltInDefaults() {
return base.useBuiltInDefaults();
}
@Override
public synchronized boolean isInitialized() {
RapidoidEnv.touch();
return base.initialized;
}
@Override
public synchronized ConfigChanges getChangesSince(Config previousConfig) {
Map prevMap;
boolean initial = previousConfig == null;
if (!initial) {
prevMap = isRoot ? previousConfig.toMap() : previousConfig.sub(keys()).toMap();
} else {
prevMap = U.map();
}
return ConfigChanges.from(keys(), prevMap, toMap(), initial);
}
@Override
public synchronized void addChangeListener(Operation configChangeListener) {
ConfigChangeListener listener = new ConfigChangeListener(keys(), configChangeListener);
base.configChangesListeners.add(listener);
// for the first time, call the listener with the current configuration as initial
root().notifyChangeListener(listener, null);
}
@Override
public synchronized void removeChangeListener(Operation configChangeListener) {
base.configChangesListeners.remove(new ConfigChangeListener(keys(), configChangeListener));
}
private synchronized void onFileSystemChange(String filename) {
mustBeRoot();
if (filename.endsWith(".yaml")
|| filename.endsWith(".yml")
|| filename.endsWith(".json")) {
Log.info("Detected configuration file changes", "file", filename);
reloadAndProcessChanges();
}
}
private void reloadAndProcessChanges() {
mustBeRoot();
Set listeners = U.set(base.configChangesListeners);
if (listeners.isEmpty()) return;
ConfigImpl previousConfig = new ConfigImpl(Coll.deepCopyOf(toMap()));
// reload the configuration
invalidate();
makeSureIsInitialized();
// notify listeners
for (ConfigChangeListener listener : listeners) {
notifyChangeListener(listener, previousConfig);
}
}
private void notifyChangeListener(ConfigChangeListener listener, Config previousConfig) {
mustBeRoot();
Config cfg = U.notEmpty(listener.keys) ? sub(listener.keys) : this;
ConfigChanges changes = cfg.getChangesSince(previousConfig);
if (changes.count() > 0) {
try {
listener.operation.execute(changes);
} catch (Exception e) {
Log.error("Error occurred in a configuration changes listener!", e);
}
}
}
@Override
public synchronized Config defaultOrCustom(String name) {
U.must(U.notEmpty(name), "The configuration name cannot be empty! Use name 'default' for the default configuration.");
if (name.equalsIgnoreCase("default")) return this;
String configKey = U.last(keys()) + "-" + name;
return parent().sub(configKey);
}
}