parent
4a2758b194
commit
98ca7fd223
24 changed files with 8 additions and 1450 deletions
gradle.properties
settings-content-json
settings-content-yaml
settings-content
build.gradle
settings.gradlesrc
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = content
|
name = content
|
||||||
version = 5.2.0
|
version = 5.3.0
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
dependencies {
|
|
||||||
api project(':settings-content')
|
|
||||||
api project(':content-json')
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.content.json.JsonSettingsLoader;
|
|
||||||
|
|
||||||
module org.xbib.settings.content.json {
|
|
||||||
exports org.xbib.settings.content.json;
|
|
||||||
requires transitive org.xbib.settings.content;
|
|
||||||
requires transitive org.xbib.content.json;
|
|
||||||
requires org.xbib.settings.api;
|
|
||||||
uses SettingsLoader;
|
|
||||||
provides SettingsLoader with JsonSettingsLoader;
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package org.xbib.settings.content.json;
|
|
||||||
|
|
||||||
import org.xbib.content.XContent;
|
|
||||||
import org.xbib.content.json.JsonXContent;
|
|
||||||
import org.xbib.settings.content.AbstractSettingsLoader;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings loader that loads (parses) the settings in a json format by flattening them
|
|
||||||
* into a map.
|
|
||||||
*/
|
|
||||||
public class JsonSettingsLoader extends AbstractSettingsLoader {
|
|
||||||
|
|
||||||
public JsonSettingsLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContent content() {
|
|
||||||
return JsonXContent.jsonContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> suffixes() {
|
|
||||||
return Set.of("json");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
org.xbib.settings.content.json.JsonSettingsLoader
|
|
|
@ -1,6 +0,0 @@
|
||||||
module org.xbib.settings.content.json.test {
|
|
||||||
requires org.junit.jupiter.api;
|
|
||||||
requires org.xbib.settings.api;
|
|
||||||
requires org.xbib.settings.content.json;
|
|
||||||
opens org.xbib.settings.content.json.test to org.junit.platform.commons;
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
package org.xbib.settings.content.json.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.xbib.settings.Settings;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.content.json.JsonSettingsLoader;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
public class JsonSettingsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapForSettings() throws IOException {
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
map.put("hello", "world");
|
|
||||||
Map<String, Object> settingsMap = new HashMap<>();
|
|
||||||
settingsMap.put("map", map);
|
|
||||||
SettingsLoader settingsLoader = new JsonSettingsLoader();
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put(settingsLoader.load(settingsMap))
|
|
||||||
.build();
|
|
||||||
assertEquals("{map.hello=world}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapSettingsFromReader() throws IOException {
|
|
||||||
Map<String, Object> map = Map.of("map", Map.of("hello", "world"));
|
|
||||||
SettingsLoader settingsLoader = new JsonSettingsLoader();
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put(settingsLoader.load(map))
|
|
||||||
.build();
|
|
||||||
assertEquals("{map.hello=world}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadFromString() throws IOException {
|
|
||||||
String json = "{\"Hello\":\"World\"}";
|
|
||||||
SettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
Map<String, String> result = loader.load(json);
|
|
||||||
assertEquals("{Hello=World}", result.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadSettingsFromString() throws IOException {
|
|
||||||
String json = "{\"Hello\":\"World\"}";
|
|
||||||
Settings settings = Settings.settingsBuilder().loadFromString("json", json).build();
|
|
||||||
assertEquals("{Hello=World}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFlatLoader() throws IOException {
|
|
||||||
String s = "{\"a\":{\"b\":\"c\"}}";
|
|
||||||
SettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
Map<String, String> flatMap = loader.load(s);
|
|
||||||
assertEquals("{a.b=c}", flatMap.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadFromMap() throws IOException {
|
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
|
||||||
Map<String, Object> code = new LinkedHashMap<>();
|
|
||||||
code.put("a", "b");
|
|
||||||
code.put("b", "c");
|
|
||||||
Map<String, Object> name = new LinkedHashMap<>();
|
|
||||||
name.put("a", "b");
|
|
||||||
name.put("b", "c");
|
|
||||||
List<String> list = Arrays.asList("a", "b");
|
|
||||||
map.put("code", code);
|
|
||||||
map.put("name", name);
|
|
||||||
map.put("list", list);
|
|
||||||
map.put("null", null);
|
|
||||||
SettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
Map<String, String> result = loader.load(map);
|
|
||||||
assertEquals("{code.a=b, code.b=c, name.a=b, name.b=c, list.0=a, list.1=b, null=null}", result.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
dependencies {
|
|
||||||
api project(':settings-content')
|
|
||||||
api project(':content-yaml')
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.content.yaml.YamlSettingsLoader;
|
|
||||||
|
|
||||||
module org.xbib.settings.content.yaml {
|
|
||||||
exports org.xbib.settings.content.yaml;
|
|
||||||
requires transitive org.xbib.settings.content;
|
|
||||||
requires transitive org.xbib.content.yaml;
|
|
||||||
requires org.xbib.settings.api;
|
|
||||||
uses SettingsLoader;
|
|
||||||
provides SettingsLoader with YamlSettingsLoader;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package org.xbib.settings.content.yaml;
|
|
||||||
|
|
||||||
import org.xbib.content.XContent;
|
|
||||||
import org.xbib.content.yaml.YamlXContent;
|
|
||||||
import org.xbib.settings.content.AbstractSettingsLoader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings loader that loads (parses) the settings in a yaml format by flattening them
|
|
||||||
* into a map.
|
|
||||||
*/
|
|
||||||
public class YamlSettingsLoader extends AbstractSettingsLoader {
|
|
||||||
|
|
||||||
private static final Set<String> YAML_SUFFIXES = Set.of("yml", "yaml");
|
|
||||||
|
|
||||||
public YamlSettingsLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContent content() {
|
|
||||||
return YamlXContent.yamlContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> suffixes() {
|
|
||||||
return YAML_SUFFIXES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> load(String source) throws IOException {
|
|
||||||
// replace tabs with whitespace (yaml does not accept tabs, but many users might use it still...)
|
|
||||||
return super.load(source.replace("\t", " "));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
org.xbib.settings.content.yaml.YamlSettingsLoader
|
|
|
@ -1,5 +0,0 @@
|
||||||
dependencies {
|
|
||||||
api libs.settings.api
|
|
||||||
api project(':content-core')
|
|
||||||
testImplementation project(":settings-content-json")
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import org.xbib.settings.SettingsBuilder;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.content.ContentSettingsBuilder;
|
|
||||||
import org.xbib.settings.content.PropertiesSettingsLoader;
|
|
||||||
|
|
||||||
module org.xbib.settings.content {
|
|
||||||
uses SettingsLoader;
|
|
||||||
provides SettingsLoader with PropertiesSettingsLoader;
|
|
||||||
uses SettingsBuilder;
|
|
||||||
provides SettingsBuilder with ContentSettingsBuilder;
|
|
||||||
exports org.xbib.settings.content;
|
|
||||||
requires transitive org.xbib.settings.api;
|
|
||||||
requires transitive org.xbib.content.core;
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
package org.xbib.settings.content;
|
|
||||||
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.content.XContent;
|
|
||||||
import org.xbib.content.XContentBuilder;
|
|
||||||
import org.xbib.content.core.DefaultXContentBuilder;
|
|
||||||
import org.xbib.content.XContentGenerator;
|
|
||||||
import org.xbib.content.XContentParser;
|
|
||||||
import org.xbib.content.io.BytesReference;
|
|
||||||
import org.xbib.content.io.BytesStreamOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings loader that loads (parses) the settings in a XContent format
|
|
||||||
* by flattening them into a map.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractSettingsLoader implements SettingsLoader {
|
|
||||||
|
|
||||||
public AbstractSettingsLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract XContent content();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> load(String source) throws IOException {
|
|
||||||
try (XContentParser parser = content().createParser(source)) {
|
|
||||||
return load(parser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> load(Map<String, Object> map) throws IOException {
|
|
||||||
XContentBuilder builder = DefaultXContentBuilder.builder(content());
|
|
||||||
builder.map(map);
|
|
||||||
return load(builder.string());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> load(XContentParser xContentParser) throws IOException {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
Map<String, String> map = new LinkedHashMap<>();
|
|
||||||
List<String> path = new ArrayList<>();
|
|
||||||
XContentParser.Token token = xContentParser.nextToken();
|
|
||||||
if (token == null) {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
parseObject(map, sb, path, xContentParser, null);
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String flatMapAsString(BytesReference bytesReference) throws IOException {
|
|
||||||
try (XContentParser parser = content().createParser(bytesReference.toBytes());
|
|
||||||
BytesStreamOutput bytesStreamOutput = new BytesStreamOutput();
|
|
||||||
XContentGenerator generator = content().createGenerator(bytesStreamOutput)) {
|
|
||||||
return flatMapAsString(parser, bytesStreamOutput, generator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String flatMapAsString(XContentParser parser,
|
|
||||||
BytesStreamOutput bytesStreamOutput,
|
|
||||||
XContentGenerator generator) throws IOException {
|
|
||||||
generator.writeStartObject();
|
|
||||||
for (Map.Entry<String, String> entry : load(parser).entrySet()) {
|
|
||||||
generator.writeFieldName(entry.getKey());
|
|
||||||
String value = entry.getValue();
|
|
||||||
if (value == null) {
|
|
||||||
generator.writeNull();
|
|
||||||
} else {
|
|
||||||
generator.writeString(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
generator.writeEndObject();
|
|
||||||
generator.flush();
|
|
||||||
return bytesStreamOutput.bytes().toUtf8();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseObject(Map<String, String> map, StringBuilder sb, List<String> path,
|
|
||||||
XContentParser parser, String objFieldName) throws IOException {
|
|
||||||
if (objFieldName != null) {
|
|
||||||
path.add(objFieldName);
|
|
||||||
}
|
|
||||||
String currentFieldName = null;
|
|
||||||
XContentParser.Token token;
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
||||||
if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
parseObject(map, sb, path, parser, currentFieldName);
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
|
||||||
parseArray(map, sb, path, parser, currentFieldName);
|
|
||||||
} else if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
currentFieldName = parser.currentName();
|
|
||||||
} else {
|
|
||||||
parseValue(map, sb, path, parser, currentFieldName);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objFieldName != null) {
|
|
||||||
path.remove(path.size() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseArray(Map<String, String> map, StringBuilder sb, List<String> path,
|
|
||||||
XContentParser parser, String name) throws IOException {
|
|
||||||
XContentParser.Token token;
|
|
||||||
int counter = 0;
|
|
||||||
String fieldName = name;
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
parseObject(map, sb, path, parser, fieldName + '.' + (counter++));
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
|
||||||
parseArray(map, sb, path, parser, fieldName + '.' + (counter++));
|
|
||||||
} else if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
fieldName = parser.currentName();
|
|
||||||
} else {
|
|
||||||
parseValue(map, sb, path, parser, fieldName + '.' + (counter++));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void parseValue(Map<String, String> map, StringBuilder sb, List<String> path,
|
|
||||||
XContentParser parser, String fieldName) throws IOException {
|
|
||||||
sb.setLength(0);
|
|
||||||
for (String s : path) {
|
|
||||||
sb.append(s).append('.');
|
|
||||||
}
|
|
||||||
sb.append(fieldName);
|
|
||||||
map.put(sb.toString(), parser.text());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,445 +0,0 @@
|
||||||
package org.xbib.settings.content;
|
|
||||||
|
|
||||||
import org.xbib.settings.Settings;
|
|
||||||
import org.xbib.settings.SettingsException;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.SettingsLoaderService;
|
|
||||||
import org.xbib.datastructures.api.ByteSizeValue;
|
|
||||||
import org.xbib.datastructures.api.TimeValue;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class ContentSettings implements Settings, AutoCloseable {
|
|
||||||
|
|
||||||
public static final String[] EMPTY_ARRAY = new String[0];
|
|
||||||
|
|
||||||
public static final int BUFFER_SIZE = 1024 * 4;
|
|
||||||
|
|
||||||
private DefaultSettingsRefresher refresher;
|
|
||||||
|
|
||||||
private Map<String, String> map;
|
|
||||||
|
|
||||||
protected ContentSettings(Map<String, String> map) {
|
|
||||||
this(map, null, 0L, 0L, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ContentSettings(Map<String, String> map, Path path, long initialDelay, long period, TimeUnit timeUnit) {
|
|
||||||
this.map = new LinkedHashMap<>(map);
|
|
||||||
if (path != null && initialDelay >= 0L && period > 0L) {
|
|
||||||
this.refresher = new DefaultSettingsRefresher(path, initialDelay, period, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ContentSettingsBuilder builder() {
|
|
||||||
return new ContentSettingsBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ContentSettings readSettingsFromMap(Map<String, Object> map) {
|
|
||||||
ContentSettingsBuilder builder = new ContentSettingsBuilder();
|
|
||||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
||||||
builder.put(entry.getKey(), entry.getValue() != null ? entry.getValue().toString() : null);
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeSettingsToMap(ContentSettings settings, Map<String, Object> map) {
|
|
||||||
for (String key : settings.getAsMap().keySet()) {
|
|
||||||
map.put(key, settings.get(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] splitStringByCommaToArray(final String s) {
|
|
||||||
return splitStringToArray(s, ',');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] splitStringToArray(final String s, final char c) {
|
|
||||||
if (s.length() == 0) {
|
|
||||||
return EMPTY_ARRAY;
|
|
||||||
}
|
|
||||||
final char[] chars = s.toCharArray();
|
|
||||||
int count = 1;
|
|
||||||
for (final char x : chars) {
|
|
||||||
if (x == c) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String[] result = new String[count];
|
|
||||||
final int len = chars.length;
|
|
||||||
int start = 0;
|
|
||||||
int pos = 0;
|
|
||||||
int i = 0;
|
|
||||||
for (; pos < len; pos++) {
|
|
||||||
if (chars[pos] == c) {
|
|
||||||
int size = pos - start;
|
|
||||||
if (size > 0) {
|
|
||||||
result[i++] = new String(chars, start, size);
|
|
||||||
}
|
|
||||||
start = pos + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int size = pos - start;
|
|
||||||
if (size > 0) {
|
|
||||||
result[i++] = new String(chars, start, size);
|
|
||||||
}
|
|
||||||
if (i != count) {
|
|
||||||
String[] result1 = new String[i];
|
|
||||||
System.arraycopy(result, 0, result1, 0, i);
|
|
||||||
return result1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String copyToString(Reader in) throws IOException {
|
|
||||||
StringWriter out = new StringWriter();
|
|
||||||
copy(in, out);
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int copy(final Reader in, final Writer out) throws IOException {
|
|
||||||
try (Reader reader = in; Writer writer = out) {
|
|
||||||
int byteCount = 0;
|
|
||||||
char[] buffer = new char[BUFFER_SIZE];
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = reader.read(buffer)) != -1) {
|
|
||||||
writer.write(buffer, 0, bytesRead);
|
|
||||||
byteCount += bytesRead;
|
|
||||||
}
|
|
||||||
writer.flush();
|
|
||||||
return byteCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> getAsMap() {
|
|
||||||
return this.map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getAsStructuredMap() {
|
|
||||||
Map<String, Object> stringObjectMap = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, String> entry : this.map.entrySet()) {
|
|
||||||
processSetting(stringObjectMap, "", entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
|
|
||||||
if (entry.getValue() instanceof Map) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, Object> valMap = (Map<String, Object>) entry.getValue();
|
|
||||||
entry.setValue(convertMapsToArrays(valMap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stringObjectMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentSettings getByPrefix(String prefix) {
|
|
||||||
ContentSettingsBuilder builder = new ContentSettingsBuilder();
|
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
|
||||||
if (entry.getKey().startsWith(prefix)) {
|
|
||||||
if (entry.getKey().length() < prefix.length()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
builder.put(entry.getKey().substring(prefix.length()), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentSettings getAsSettings(String setting) {
|
|
||||||
return getByPrefix(setting + ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsSetting(String setting) {
|
|
||||||
if (map.containsKey(setting)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
|
||||||
if (entry.getKey().startsWith(setting)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return map.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get(String setting) {
|
|
||||||
return map.get(setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get(String setting, String defaultValue) {
|
|
||||||
String retVal = map.get(setting);
|
|
||||||
return retVal == null ? defaultValue : retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAsInt(String setting, int 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean getAsBoolean(String setting, boolean defaultValue) {
|
|
||||||
String value = get(setting);
|
|
||||||
if (value == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return !("false".equals(value) || "0".equals(value) || "off".equals(value) || "no".equals(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TimeValue getAsTime(String setting, TimeValue defaultValue) {
|
|
||||||
return TimeValue.parseTimeValue(get(setting), defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteSizeValue getAsBytesSize(String setting, ByteSizeValue defaultValue) {
|
|
||||||
return ByteSizeValue.parseBytesSizeValue(get(setting), defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getAsArray(String settingPrefix) {
|
|
||||||
return getAsArray(settingPrefix, EMPTY_ARRAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getAsArray(String settingPrefix, String[] defaultArray) {
|
|
||||||
List<String> result = new ArrayList<>();
|
|
||||||
if (get(settingPrefix) != null) {
|
|
||||||
String[] strings = splitStringByCommaToArray(get(settingPrefix));
|
|
||||||
if (strings.length > 0) {
|
|
||||||
for (String string : strings) {
|
|
||||||
result.add(string.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int counter = 0;
|
|
||||||
while (true) {
|
|
||||||
String value = get(settingPrefix + '.' + (counter++));
|
|
||||||
if (value == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result.add(value.trim());
|
|
||||||
}
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
return defaultArray;
|
|
||||||
}
|
|
||||||
return result.toArray(new String[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Settings> getGroups(String prefix) {
|
|
||||||
String settingPrefix = prefix;
|
|
||||||
if (settingPrefix.charAt(settingPrefix.length() - 1) != '.') {
|
|
||||||
settingPrefix = settingPrefix + ".";
|
|
||||||
}
|
|
||||||
// we don't really care that it might happen twice
|
|
||||||
Map<String, Map<String, String>> hashMap = new LinkedHashMap<>();
|
|
||||||
for (String o : this.map.keySet()) {
|
|
||||||
if (o.startsWith(settingPrefix)) {
|
|
||||||
String nameValue = o.substring(settingPrefix.length());
|
|
||||||
int dotIndex = nameValue.indexOf('.');
|
|
||||||
if (dotIndex == -1) {
|
|
||||||
throw new SettingsException("Failed to get setting group for ["
|
|
||||||
+ settingPrefix
|
|
||||||
+ "] setting prefix and setting [" + o + "] because of a missing '.'");
|
|
||||||
}
|
|
||||||
String name = nameValue.substring(0, dotIndex);
|
|
||||||
String value = nameValue.substring(dotIndex + 1);
|
|
||||||
Map<String, String> groupSettings = hashMap.computeIfAbsent(name, k -> new LinkedHashMap<>());
|
|
||||||
groupSettings.put(value, get(o));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, Settings> retVal = new LinkedHashMap<>();
|
|
||||||
for (Map.Entry<String, Map<String, String>> entry : hashMap.entrySet()) {
|
|
||||||
retVal.put(entry.getKey(), new ContentSettings(entry.getValue()));
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return this == o || !(o == null || getClass() != o.getClass()) && map.equals(((ContentSettings) o).map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return map.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (refresher != null) {
|
|
||||||
refresher.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processSetting(Map<String, Object> map, String prefix, String setting, String value) {
|
|
||||||
int prefixLength = setting.indexOf('.');
|
|
||||||
if (prefixLength == -1) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, Object> innerMap = (Map<String, Object>) map.get(prefix + setting);
|
|
||||||
if (innerMap != null) {
|
|
||||||
for (Map.Entry<String, Object> 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<String, Object> newMap = new LinkedHashMap<>();
|
|
||||||
processSetting(newMap, "", rest, value);
|
|
||||||
map.put(key, newMap);
|
|
||||||
} else {
|
|
||||||
if (existingValue instanceof Map) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, Object> innerMap = (Map<String, Object>) existingValue;
|
|
||||||
processSetting(innerMap, "", rest, value);
|
|
||||||
map.put(key, innerMap);
|
|
||||||
} else {
|
|
||||||
processSetting(map, prefix + key + ".", rest, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object convertMapsToArrays(Map<String, Object> map) {
|
|
||||||
if (map.isEmpty()) {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
boolean isArray = true;
|
|
||||||
int maxIndex = -1;
|
|
||||||
for (Map.Entry<String, Object> 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<String, Object> valMap = (Map<String, Object>) entry.getValue();
|
|
||||||
entry.setValue(convertMapsToArrays(valMap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isArray && (maxIndex + 1) == map.size()) {
|
|
||||||
ArrayList<Object> newValue = new ArrayList<>(maxIndex + 1);
|
|
||||||
for (int i = 0; i <= maxIndex; i++) {
|
|
||||||
Object obj = map.get(Integer.toString(i));
|
|
||||||
if (obj == null) {
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
newValue.add(obj);
|
|
||||||
}
|
|
||||||
return newValue;
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DefaultSettingsRefresher implements Runnable {
|
|
||||||
|
|
||||||
private final Path path;
|
|
||||||
|
|
||||||
private final ScheduledExecutorService executorService;
|
|
||||||
|
|
||||||
private final AtomicBoolean closed;
|
|
||||||
|
|
||||||
DefaultSettingsRefresher(Path path, long initialDelay, long period, TimeUnit timeUnit) {
|
|
||||||
this.path = path;
|
|
||||||
this.executorService = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
executorService.scheduleAtFixedRate(this, initialDelay, period, timeUnit);
|
|
||||||
this.closed = new AtomicBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
if (!closed.get()) {
|
|
||||||
String settingsSource = Files.readString(path);
|
|
||||||
SettingsLoader settingsLoader = SettingsLoaderService.getInstance().loaderFromResource(path.toString());
|
|
||||||
map = settingsLoader.load(settingsSource);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("unable to refresh settings from path " + path, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
closed.set(true);
|
|
||||||
executorService.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,359 +0,0 @@
|
||||||
package org.xbib.settings.content;
|
|
||||||
|
|
||||||
import org.xbib.settings.PlaceholderResolver;
|
|
||||||
import org.xbib.settings.PropertyPlaceholder;
|
|
||||||
import org.xbib.settings.Settings;
|
|
||||||
import org.xbib.settings.SettingsBuilder;
|
|
||||||
import org.xbib.settings.SettingsException;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.SettingsLoaderService;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.DateTimeException;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class ContentSettingsBuilder implements SettingsBuilder {
|
|
||||||
|
|
||||||
private final SettingsLoaderService settingsLoaderService;
|
|
||||||
|
|
||||||
private final Map<String, String> map;
|
|
||||||
|
|
||||||
private Path path;
|
|
||||||
|
|
||||||
private long initialDelay;
|
|
||||||
|
|
||||||
private long period;
|
|
||||||
|
|
||||||
private TimeUnit timeUnit;
|
|
||||||
|
|
||||||
public ContentSettingsBuilder() {
|
|
||||||
this.settingsLoaderService = SettingsLoaderService.getInstance();
|
|
||||||
this.map = new LinkedHashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String remove(String key) {
|
|
||||||
return map.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return map.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a setting with the provided setting key and value.
|
|
||||||
*
|
|
||||||
* @param key The setting key
|
|
||||||
* @param value The setting value
|
|
||||||
* @return The builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder put(String key, String value) {
|
|
||||||
map.put(key, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a setting with the provided setting key and class as value.
|
|
||||||
*
|
|
||||||
* @param key The setting key
|
|
||||||
* @param clazz The setting class value
|
|
||||||
* @return The builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder put(String key, Class<?> clazz) {
|
|
||||||
map.put(key, clazz.getName());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ContentSettingsBuilder 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 ContentSettingsBuilder put(String setting, int value) {
|
|
||||||
put(setting, String.valueOf(value));
|
|
||||||
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 ContentSettingsBuilder 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 ContentSettingsBuilder 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 ContentSettingsBuilder put(String setting, double value) {
|
|
||||||
put(setting, String.valueOf(value));
|
|
||||||
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 ContentSettingsBuilder putArray(String setting, String... values) {
|
|
||||||
remove(setting);
|
|
||||||
int counter = 0;
|
|
||||||
while (true) {
|
|
||||||
String value = map.remove(setting + '.' + (counter++));
|
|
||||||
if (value == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
put(setting + '.' + i, values[i]);
|
|
||||||
}
|
|
||||||
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 ContentSettingsBuilder putArray(String setting, List<String> values) {
|
|
||||||
remove(setting);
|
|
||||||
int counter = 0;
|
|
||||||
while (true) {
|
|
||||||
String value = map.remove(setting + '.' + (counter++));
|
|
||||||
if (value == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < values.size(); i++) {
|
|
||||||
put(setting + '.' + i, values.get(i));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the setting group.
|
|
||||||
*
|
|
||||||
* @param settingPrefix setting prefix
|
|
||||||
* @param groupName group name
|
|
||||||
* @param settings settings
|
|
||||||
* @param values values
|
|
||||||
* @return a builder
|
|
||||||
* @throws SettingsException if setting fails
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder put(String settingPrefix, String groupName, String[] settings, String[] values)
|
|
||||||
throws SettingsException {
|
|
||||||
if (settings.length != values.length) {
|
|
||||||
throw new SettingsException("The settings length must match the value length");
|
|
||||||
}
|
|
||||||
for (int i = 0; i < settings.length; i++) {
|
|
||||||
if (values[i] == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
put(settingPrefix + "" + groupName + "." + settings[i], values[i]);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all the provided settings.
|
|
||||||
*
|
|
||||||
* @param settings settings
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder put(Settings settings) {
|
|
||||||
map.putAll(settings.getAsMap());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all the provided settings.
|
|
||||||
*
|
|
||||||
* @param settings settings
|
|
||||||
* @return a builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder put(Map<String, String> settings) {
|
|
||||||
map.putAll(settings);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads settings from an URL.
|
|
||||||
*
|
|
||||||
* @param url url
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder loadFromUrl(URL url) throws SettingsException {
|
|
||||||
try {
|
|
||||||
return loadFromResource(url.toExternalForm(), url.openStream());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new SettingsException("Failed to open stream for url [" + url.toExternalForm() + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads settings from a stream.
|
|
||||||
*
|
|
||||||
* @param resourceName resource name
|
|
||||||
* @param inputStream input stream
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ContentSettingsBuilder loadFromResource(String resourceName, InputStream inputStream) {
|
|
||||||
try {
|
|
||||||
return loadFromString(resourceName, ContentSettings.copyToString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SettingsException("failed to load settings from [" + resourceName + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads settings from the actual string content that represents them using the
|
|
||||||
* {@link SettingsLoaderService#loaderFromResource(String)} (String)}.
|
|
||||||
*
|
|
||||||
* @param resourceName the resource name ("json", "yaml")
|
|
||||||
* @param content the content
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ContentSettingsBuilder loadFromString(String resourceName, String content) {
|
|
||||||
try {
|
|
||||||
SettingsLoader settingsLoader = settingsLoaderService.loaderFromResource(resourceName);
|
|
||||||
put(settingsLoader.load(content));
|
|
||||||
return this;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SettingsException("failed to load settings from [" + resourceName + "]", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load system properties to this settings.
|
|
||||||
*
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ContentSettingsBuilder loadFromSystemProperties() {
|
|
||||||
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
|
|
||||||
put((String) entry.getKey(), (String) entry.getValue());
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load system environment to this settings.
|
|
||||||
*
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder loadFromSystemEnvironment() {
|
|
||||||
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
|
|
||||||
put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs across all the settings set on this builder and replaces {@code ${...}} elements in the
|
|
||||||
* each setting value according to the following logic:
|
|
||||||
* <p>
|
|
||||||
* First, tries to resolve it against a System property ({@link System#getProperty(String)}), next,
|
|
||||||
* tries and resolve it against an environment variable ({@link System#getenv(String)}), next,
|
|
||||||
* tries and resolve it against a date pattern to resolve the current date,
|
|
||||||
* and last, tries and replace it with another setting already set on this builder.
|
|
||||||
*
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
public ContentSettingsBuilder replacePropertyPlaceholders() {
|
|
||||||
return replacePropertyPlaceholders(new PropertyPlaceholder("${", "}", false),
|
|
||||||
placeholderName -> {
|
|
||||||
// system property
|
|
||||||
String value = System.getProperty(placeholderName);
|
|
||||||
if (value != null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
// environment
|
|
||||||
value = System.getenv(placeholderName);
|
|
||||||
if (value != null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
// current date
|
|
||||||
try {
|
|
||||||
return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now());
|
|
||||||
} catch (IllegalArgumentException | DateTimeException e) {
|
|
||||||
return map.get(placeholderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentSettingsBuilder replacePropertyPlaceholders(PropertyPlaceholder propertyPlaceholder,
|
|
||||||
PlaceholderResolver placeholderResolver) {
|
|
||||||
map.replaceAll((k, v) -> propertyPlaceholder.replacePlaceholders(v, placeholderResolver));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentSettingsBuilder setRefresh(Path path, long initialDelay, long period, TimeUnit timeUnit) {
|
|
||||||
this.path = path;
|
|
||||||
this.initialDelay = initialDelay;
|
|
||||||
this.period = period;
|
|
||||||
this.timeUnit = timeUnit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SettingsBuilder map(Function<Map.Entry<String, String>, Map.Entry<String, String>> function) {
|
|
||||||
map.entrySet().stream().map(function).forEach(e -> put(e.getKey(), e.getValue()));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentSettings build() {
|
|
||||||
return new ContentSettings(map, path, initialDelay, period, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package org.xbib.settings.content;
|
|
||||||
|
|
||||||
import org.xbib.content.io.BytesReference;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings loader that loads (parses) the settings in a properties format.
|
|
||||||
*/
|
|
||||||
public class PropertiesSettingsLoader implements SettingsLoader {
|
|
||||||
|
|
||||||
private static final Set<String> PROPERTIES_SUFFIXES = new HashSet<>(Collections.singletonList("properties"));
|
|
||||||
|
|
||||||
public PropertiesSettingsLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> suffixes() {
|
|
||||||
return PROPERTIES_SUFFIXES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> load(String source) throws IOException {
|
|
||||||
Properties props = new Properties();
|
|
||||||
try (StringReader reader = new StringReader(source)) {
|
|
||||||
props.load(reader);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
|
||||||
result.put((String) entry.getKey(), (String) entry.getValue());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String> load(Map<String, Object> source) {
|
|
||||||
Properties props = new Properties();
|
|
||||||
props.putAll(source);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
|
||||||
result.put((String) entry.getKey(), (String) entry.getValue());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> load(BytesReference ref) throws IOException {
|
|
||||||
Properties props = new Properties();
|
|
||||||
try (Reader reader = new InputStreamReader(ref.streamInput(), StandardCharsets.UTF_8)) {
|
|
||||||
props.load(reader);
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
|
||||||
result.put((String) entry.getKey(), (String) entry.getValue());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/**
|
|
||||||
* Classes for settings.
|
|
||||||
*/
|
|
||||||
package org.xbib.settings.content;
|
|
|
@ -1 +0,0 @@
|
||||||
org.xbib.settings.content.ContentSettingsBuilder
|
|
|
@ -1,8 +0,0 @@
|
||||||
module org.xbib.settings.content.test {
|
|
||||||
requires org.junit.jupiter.api;
|
|
||||||
requires org.xbib.content.core;
|
|
||||||
requires org.xbib.content.json;
|
|
||||||
requires org.xbib.settings.api;
|
|
||||||
requires org.xbib.settings.content.json;
|
|
||||||
opens org.xbib.settings.content.test to org.junit.platform.commons;
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
package org.xbib.settings.content.test;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.xbib.content.core.XContentHelper;
|
|
||||||
import org.xbib.content.io.BytesArray;
|
|
||||||
import org.xbib.content.io.BytesReference;
|
|
||||||
import org.xbib.content.json.JsonXContent;
|
|
||||||
import org.xbib.settings.Settings;
|
|
||||||
import org.xbib.settings.SettingsLoader;
|
|
||||||
import org.xbib.settings.content.json.JsonSettingsLoader;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
public class SettingsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEmpty() {
|
|
||||||
Settings settings = Settings.emptySettings();
|
|
||||||
assertTrue(settings.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testArray() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.putArray("input", Arrays.asList("a", "b", "c")).build();
|
|
||||||
assertEquals("a", settings.getAsArray("input")[0]);
|
|
||||||
assertEquals("b", settings.getAsArray("input")[1]);
|
|
||||||
assertEquals("c", settings.getAsArray("input")[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Test
|
|
||||||
public void testArrayOfMaps() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put("location.0.code", "Code 0")
|
|
||||||
.put("location.0.name", "Name 0")
|
|
||||||
.put("location.1.code", "Code 1")
|
|
||||||
.put("location.1.name", "Name 1")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// turn map with index keys 0,1,... into a list of maps
|
|
||||||
Map<String, Object> map = settings.getAsSettings("location").getAsStructuredMap();
|
|
||||||
List<Map<String, Object>> list = new ArrayList<>();
|
|
||||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
||||||
list.add((Map<String, Object>) entry.getValue());
|
|
||||||
}
|
|
||||||
assertEquals("[{code=Code 0, name=Name 0}, {code=Code 1, name=Name 1}]", list.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGroups() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put("prefix.group1.k1", "v1")
|
|
||||||
.put("prefix.group1.k2", "v2")
|
|
||||||
.put("prefix.group1.k3", "v3")
|
|
||||||
.put("prefix.group2.k1", "v1")
|
|
||||||
.put("prefix.group2.k2", "v2")
|
|
||||||
.put("prefix.group2.k3", "v3")
|
|
||||||
.build();
|
|
||||||
Map<String, Settings> groups = settings.getGroups("prefix");
|
|
||||||
assertEquals("[group1, group2]", groups.keySet().toString());
|
|
||||||
assertTrue(groups.get("group1").getAsMap().containsKey("k1"));
|
|
||||||
assertTrue(groups.get("group1").getAsMap().containsKey("k2"));
|
|
||||||
assertTrue(groups.get("group1").getAsMap().containsKey("k3"));
|
|
||||||
assertTrue(groups.get("group2").getAsMap().containsKey("k1"));
|
|
||||||
assertTrue(groups.get("group2").getAsMap().containsKey("k2"));
|
|
||||||
assertTrue(groups.get("group2").getAsMap().containsKey("k3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapForSettings() throws IOException {
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
map.put("hello", "world");
|
|
||||||
Map<String, Object> settingsMap = new HashMap<>();
|
|
||||||
settingsMap.put("map", map);
|
|
||||||
SettingsLoader settingsLoader = new JsonSettingsLoader();
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put(settingsLoader.load(settingsMap)).build();
|
|
||||||
assertEquals("{map.hello=world}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapSettingsFromReader() throws IOException {
|
|
||||||
StringReader reader = new StringReader("{\"map\":{\"hello\":\"world\"}}");
|
|
||||||
Map<String, Object> map = XContentHelper.convertFromContentToMap(JsonXContent.jsonContent(), reader);
|
|
||||||
SettingsLoader settingsLoader = new JsonSettingsLoader();
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put(settingsLoader.load(map))
|
|
||||||
.build();
|
|
||||||
assertEquals("{map.hello=world}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCurrentYearInSettings() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put("date", "${yyyy}")
|
|
||||||
.replacePropertyPlaceholders()
|
|
||||||
.build();
|
|
||||||
assertEquals(LocalDate.now().getYear(), Integer.parseInt(settings.get("date")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPropertyReplaceNull() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put("null", null)
|
|
||||||
.replacePropertyPlaceholders()
|
|
||||||
.build();
|
|
||||||
assertNull(settings.get("null"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSystemEnvironment() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.loadFromSystemEnvironment()
|
|
||||||
.build();
|
|
||||||
assertFalse(settings.getAsMap().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSystemProperties() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.loadFromSystemProperties()
|
|
||||||
.build();
|
|
||||||
assertFalse(settings.getAsMap().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPropertiesLoaderFromResource() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.loadFromResource("properties", new ByteArrayInputStream("a.b=c".getBytes(StandardCharsets.UTF_8)))
|
|
||||||
.build();
|
|
||||||
assertEquals("{a.b=c}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPropertiesLoaderFromString() {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.loadFromString("properties", "#\na.b=c")
|
|
||||||
.build();
|
|
||||||
assertEquals("{a.b=c}", settings.getAsMap().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFlatLoader() throws IOException {
|
|
||||||
String s = "{\"a\":{\"b\":\"c\"}}";
|
|
||||||
JsonSettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
Map<String, String> flatMap = loader.load(s);
|
|
||||||
assertEquals("{a.b=c}", flatMap.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFlatLoaderToJsonString() throws IOException {
|
|
||||||
String s = "{\"a\":{\"b\":\"c\"}}";
|
|
||||||
JsonSettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
String result = JsonXContent.contentBuilder().flatMap(loader.load(s)).string();
|
|
||||||
assertEquals("{\"a.b\":\"c\"}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFlatMapAsString() throws IOException {
|
|
||||||
String s = "{\"a\":{\"b\":\"c\"}}";
|
|
||||||
BytesReference ref = new BytesArray(s.getBytes(StandardCharsets.UTF_8));
|
|
||||||
JsonSettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
String result = loader.flatMapAsString(ref);
|
|
||||||
assertEquals("{\"a.b\":\"c\"}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadFromMap() throws IOException {
|
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
|
||||||
Map<String, Object> code = new LinkedHashMap<>();
|
|
||||||
code.put("a", "b");
|
|
||||||
code.put("b", "c");
|
|
||||||
Map<String, Object> name = new LinkedHashMap<>();
|
|
||||||
name.put("a", "b");
|
|
||||||
name.put("b", "c");
|
|
||||||
List<String> list = Arrays.asList("a","b");
|
|
||||||
map.put("code", code);
|
|
||||||
map.put("name", name);
|
|
||||||
map.put("list", list);
|
|
||||||
map.put("null", null);
|
|
||||||
JsonSettingsLoader loader = new JsonSettingsLoader();
|
|
||||||
Map<String, String> result = loader.load(map);
|
|
||||||
assertEquals("{code.a=b, code.b=c, name.a=b, name.b=c, list.0=a, list.1=b, null=null}", result.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled("no refresh supported")
|
|
||||||
@Test
|
|
||||||
public void testRefresher() throws Exception {
|
|
||||||
Settings settings = Settings.settingsBuilder()
|
|
||||||
.put("name", "hello")
|
|
||||||
.setRefresh(Paths.get("src/test/resources/settings.json"), 1L, 1L, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
assertEquals("hello", settings.get("name"));
|
|
||||||
Thread.sleep(2000L);
|
|
||||||
assertEquals("world", settings.get("name"));
|
|
||||||
settings.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
org.xbib.settings.content.PropertiesSettingsLoader
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"name": "world"
|
|
||||||
}
|
|
|
@ -16,27 +16,26 @@ dependencyResolutionManagement {
|
||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
libs {
|
libs {
|
||||||
version('gradle', '8.7')
|
version('gradle', '8.7')
|
||||||
version('jackson', '2.17.0')
|
version('jackson', '2.18.3')
|
||||||
version('settings', '5.0.7')
|
version('settings', '5.0.7')
|
||||||
version('net', '4.3.2')
|
version('net', '4.8.0')
|
||||||
library('jackson-core', 'com.fasterxml.jackson.core', 'jackson-core').versionRef('jackson')
|
library('jackson-core', 'com.fasterxml.jackson.core', 'jackson-core').versionRef('jackson')
|
||||||
library('jackson-databind', 'com.fasterxml.jackson.core', 'jackson-databind').versionRef('jackson')
|
library('jackson-databind', 'com.fasterxml.jackson.core', 'jackson-databind').versionRef('jackson')
|
||||||
library('jackson-dataformat-smile', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-smile').versionRef('jackson')
|
library('jackson-dataformat-smile', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-smile').versionRef('jackson')
|
||||||
library('jackson-dataformat-xml', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-xml').versionRef('jackson')
|
library('jackson-dataformat-xml', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-xml').versionRef('jackson')
|
||||||
library('jackson-dataformat-yaml', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-yaml').versionRef('jackson')
|
library('jackson-dataformat-yaml', 'com.fasterxml.jackson.dataformat', 'jackson-dataformat-yaml').versionRef('jackson')
|
||||||
library('woodstox', 'com.fasterxml.woodstox', 'woodstox-core').version('6.6.2')
|
library('woodstox', 'com.fasterxml.woodstox', 'woodstox-core').version('7.1.0')
|
||||||
library('snakeyaml', 'org.yaml', 'snakeyaml').version('2.2')
|
library('snakeyaml', 'org.yaml', 'snakeyaml').version('2.4')
|
||||||
library('settings-api', 'org.xbib', 'settings-api').versionRef('settings')
|
|
||||||
library('net', 'org.xbib', 'net').versionRef('net')
|
library('net', 'org.xbib', 'net').versionRef('net')
|
||||||
}
|
}
|
||||||
testLibs {
|
testLibs {
|
||||||
version('junit', '5.10.2')
|
version('junit', '5.12.0')
|
||||||
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
|
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
|
||||||
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
|
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
|
||||||
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
||||||
library('junit-jupiter-platform-launcher', 'org.junit.platform', 'junit-platform-launcher').version('1.10.0')
|
library('junit-jupiter-platform-launcher', 'org.junit.platform', 'junit-platform-launcher').version('1.12.0')
|
||||||
library('junit4', 'junit', 'junit').version('4.13.2')
|
library('junit4', 'junit', 'junit').version('4.13.2')
|
||||||
library('hamcrest', 'org.hamcrest:hamcrest-library:2.2')
|
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('3.0')
|
||||||
library('mockito-core', 'org.mockito', 'mockito-core').version('5.11.0')
|
library('mockito-core', 'org.mockito', 'mockito-core').version('5.11.0')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +51,3 @@ include 'content-resource'
|
||||||
include 'content-smile'
|
include 'content-smile'
|
||||||
include 'content-xml'
|
include 'content-xml'
|
||||||
include 'content-yaml'
|
include 'content-yaml'
|
||||||
include 'settings-content'
|
|
||||||
include 'settings-content-json'
|
|
||||||
include 'settings-content-yaml'
|
|
||||||
|
|
Loading…
Reference in a new issue