Turn config to valid yaml

- Moved all config settings with values in non leaf-nodes to leaf-nodes
  - Time units (Now .Time and .Unit)
  - Feature toggles (Now .Enabled)
- Wrote tests to ensure non-leaf node values are not used

Affects issues:
- Fixed #1363
This commit is contained in:
Rsl1122 2022-07-09 19:07:29 +03:00
parent ea57d0dea1
commit 2b478e58c0
10 changed files with 215 additions and 51 deletions

View File

@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
@ -239,7 +240,9 @@ public class ConfigNode {
}
public <T> void set(T value) {
if (value instanceof ConfigNode) {
if (value == null) {
this.value = null;
} else if (value instanceof ConfigNode) {
copyAll((ConfigNode) value);
} else {
ConfigValueParser<T> parser = ConfigValueParser.getParserFor(value.getClass());
@ -277,7 +280,7 @@ public class ConfigNode {
public Double getDouble() {
return value == null ? null
: new ConfigValueParser.DoubleParser().compose(value);
: new ConfigValueParser.DoubleParser().compose(value);
}
public boolean getBoolean() {
@ -318,6 +321,19 @@ public class ConfigNode {
return configPaths;
}
public <T> List<T> dfs(BiConsumer<ConfigNode, List<T>> accessVisitor) {
ArrayDeque<ConfigNode> dfs = new ArrayDeque<>();
dfs.push(this);
List<T> result = new ArrayList<>();
while (!dfs.isEmpty()) {
ConfigNode next = dfs.pop();
accessVisitor.accept(next, result);
dfs.addAll(next.getChildren());
}
return result;
}
public Integer getInteger(String path) {
return getNode(path).map(ConfigNode::getInteger).orElse(null);
}
@ -376,6 +392,11 @@ public class ConfigNode {
}
}
public void copyValue(ConfigNode from) {
comment = from.comment;
value = from.value;
}
protected int getNodeDepth() {
return parent != null ? parent.getNodeDepth() + 1 : -1; // Root node is -1
}

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.settings.config.changes;
import com.djrapitops.plan.settings.config.Config;
import com.djrapitops.plan.settings.config.ConfigNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
@ -88,6 +89,44 @@ public interface ConfigChange {
}
}
class MovedValue implements ConfigChange {
final String oldPath;
final String newPath;
public MovedValue(String oldPath, String newPath) {
this.oldPath = oldPath;
this.newPath = newPath;
}
@Override
public boolean hasBeenApplied(Config config) {
return config.getNode(oldPath)
.map(ConfigNode::getString)
.isEmpty()
&& config.getNode(newPath).isPresent();
}
@Override
public void apply(Config config) {
Optional<ConfigNode> oldNode = config.getNode(oldPath);
if (oldNode.isPresent()) {
ConfigNode node = oldNode.get();
config.getNode(newPath)
.orElseGet(() -> config.addNode(newPath))
.copyValue(node);
// Set value to null
node.set(null);
node.setComment(new ArrayList<>());
}
}
@Override
public String getAppliedMessage() {
return "Moved " + oldPath + " to " + newPath;
}
}
class Copied extends Removed {
final String newPath;

View File

@ -149,7 +149,29 @@ public class ConfigUpdater {
new ConfigChange.Removed("Database.H2.Password"),
new ConfigChange.Removed("Database.H2"),
new ConfigChange.MoveLevelDown("World_aliases", "World_aliases.List")
new ConfigChange.MoveLevelDown("World_aliases", "World_aliases.List"),
new ConfigChange.MovedValue("Webserver.Alternative_IP", "Webserver.Alternative_IP.Enabled"),
new ConfigChange.MovedValue("Webserver.Security.IP_whitelist", "Webserver.Security.IP_whitelist.Enabled"),
new ConfigChange.Moved("Formatting.Dates.Show_recent_day_names.DatePattern", "Formatting.Dates.Show_recent_day_names_date_pattern"),
new ConfigChange.MovedValue("Export.Server_refresh_period", "Export.Server_refresh_period.Time"),
new ConfigChange.MovedValue("Webserver.Security.Cookies_expire_after", "Webserver.Security.Cookies_expire_after.Time"),
new ConfigChange.MovedValue("Webserver.Cache.Reduced_refresh_barrier", "Webserver.Cache.Reduced_refresh_barrier.Time"),
new ConfigChange.MovedValue("Webserver.Cache.Invalidate_disk_cache_after", "Webserver.Cache.Invalidate_disk_cache_after.Time"),
new ConfigChange.MovedValue("Webserver.Cache.Invalidate_memory_cache_after", "Webserver.Cache.Invalidate_memory_cache_after.Time"),
new ConfigChange.MovedValue("Webserver.Cache.Invalidate_query_results_on_disk_after", "Webserver.Cache.Invalidate_query_results_on_disk_after.Time"),
new ConfigChange.MovedValue("Time.Thresholds.Remove_disabled_extension_data_after", "Time.Thresholds.Remove_disabled_extension_data_after.Time"),
new ConfigChange.MovedValue("Time.Thresholds.Remove_time_series_data_after", "Time.Thresholds.Remove_time_series_data_after.Time"),
new ConfigChange.MovedValue("Time.Thresholds.Remove_ping_data_after", "Time.Thresholds.Remove_ping_data_after.Time"),
new ConfigChange.MovedValue("Time.Thresholds.AFK_threshold", "Time.Thresholds.AFK_threshold.Time"),
new ConfigChange.MovedValue("Time.Thresholds.Remove_inactive_player_data_after", "Time.Thresholds.Remove_inactive_player_data_after.Time"),
new ConfigChange.MovedValue("Time.Periodic_tasks.Extension_data_refresh_every", "Time.Periodic_tasks.Extension_data_refresh_every.Time"),
new ConfigChange.MovedValue("Time.Periodic_tasks.Check_DB_for_server_config_files_every", "Time.Periodic_tasks.Check_DB_for_server_config_files_every.Time"),
new ConfigChange.MovedValue("Time.Periodic_tasks.Clean_Database_every", "Time.Periodic_tasks.Clean_Database_every.Time"),
new ConfigChange.MovedValue("Time.Delays.Ping_server_enable_delay", "Time.Delays.Ping_server_enable_delay.Time"),
new ConfigChange.MovedValue("Time.Delays.Ping_player_join_delay", "Time.Delays.Ping_player_join_delay.Time"),
new ConfigChange.MovedValue("Time.Delays.Wait_for_DB_Transactions_on_disable", "Time.Delays.Wait_for_DB_Transactions_on_disable.Time"),
new ConfigChange.MovedValue("Time.Thresholds.Activity_index.Playtime_threshold", "Time.Thresholds.Activity_index.Playtime_threshold.Time"),
};
}

View File

@ -29,7 +29,7 @@ public class FormatSettings {
public static final Setting<String> DECIMALS = new StringSetting("Formatting.Decimal_points");
public static final Setting<Boolean> DATE_RECENT_DAYS = new BooleanSetting("Formatting.Dates.Show_recent_day_names");
public static final Setting<String> DATE_RECENT_DAYS_PATTERN = new StringSetting("Formatting.Dates.Show_recent_day_names.DatePattern");
public static final Setting<String> DATE_RECENT_DAYS_PATTERN = new StringSetting("Formatting.Dates.Show_recent_day_names_date_pattern");
public static final Setting<String> DATE_FULL = new StringSetting("Formatting.Dates.Full");
public static final Setting<String> DATE_NO_SECONDS = new StringSetting("Formatting.Dates.NoSeconds");
public static final Setting<String> DATE_CLOCK = new StringSetting("Formatting.Dates.JustClock");

View File

@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
public class WebserverSettings {
public static final Setting<Integer> PORT = new IntegerSetting("Webserver.Port");
public static final Setting<Boolean> SHOW_ALTERNATIVE_IP = new BooleanSetting("Webserver.Alternative_IP");
public static final Setting<Boolean> SHOW_ALTERNATIVE_IP = new BooleanSetting("Webserver.Alternative_IP.Enabled");
public static final Setting<String> ALTERNATIVE_IP = new StringSetting("Webserver.Alternative_IP.Address");
public static final Setting<String> INTERNAL_IP = new StringSetting("Webserver.Internal_IP");
public static final Setting<String> CORS_ALLOW_ORIGIN = new StringSetting("Webserver.Security.CORS.Allow_origin");
@ -38,7 +38,7 @@ public class WebserverSettings {
public static final Setting<String> CERTIFICATE_STOREPASS = new StringSetting("Webserver.Security.SSL_certificate.Store_pass");
public static final Setting<String> CERTIFICATE_ALIAS = new StringSetting("Webserver.Security.SSL_certificate.Alias");
public static final Setting<Boolean> IP_USE_X_FORWARDED_FOR = new BooleanSetting("Webserver.Security.Use_X-Forwarded-For_Header");
public static final Setting<Boolean> IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist");
public static final Setting<Boolean> IP_WHITELIST = new BooleanSetting("Webserver.Security.IP_whitelist.Enabled");
public static final Setting<List<String>> WHITELIST = new StringListSetting("Webserver.Security.IP_whitelist.Whitelist");
public static final Setting<Boolean> DISABLED = new BooleanSetting("Webserver.Disable_Webserver");
public static final Setting<Boolean> DISABLED_AUTHENTICATION = new BooleanSetting("Webserver.Security.Disable_authentication");

View File

@ -48,7 +48,7 @@ public class TimeSetting extends Setting<Long> {
@Override
public Long getValueFrom(ConfigNode node) {
Long duration = node.getLong(path);
Long duration = node.getLong(path + ".Time");
if (duration == null) {
return null;
}

View File

@ -39,20 +39,25 @@ Database:
# -----------------------------------------------------
Webserver:
Port: 8804
Alternative_IP: false
Alternative_IP:
Enabled: false
# %port% is replaced automatically with Webserver.Port
Address: your.domain.here:%port%
# InternalIP usually does not need to be changed, only change it if you know what you're doing!
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
Internal_IP: 0.0.0.0
Cache:
Reduced_refresh_barrier: 15
Reduced_refresh_barrier:
Time: 15
Unit: SECONDS
Invalidate_query_results_on_disk_after: 7
Invalidate_query_results_on_disk_after:
Time: 7
Unit: DAYS
Invalidate_disk_cache_after: 2
Invalidate_disk_cache_after:
Time: 2
Unit: DAYS
Invalidate_memory_cache_after: 5
Invalidate_memory_cache_after:
Time: 5
Unit: MINUTES
Security:
SSL_certificate:
@ -72,12 +77,14 @@ Webserver:
Access_log:
Print_to_console: false
Remove_logs_after_days: 30
IP_whitelist: false
IP_whitelist:
Enabled: false
Whitelist:
- "192.168.0.0"
- "0:0:0:0:0:0:0:1"
# Does not affect existing cookies
Cookies_expire_after: 2
Cookies_expire_after:
Time: 2
Unit: HOURS
Disable_Webserver: false
External_Webserver_address: "https://www.example.address"
@ -94,37 +101,49 @@ Data_gathering:
# -----------------------------------------------------
Time:
Delays:
Ping_server_enable_delay: 300
Ping_server_enable_delay:
Time: 300
Unit: SECONDS
Ping_player_join_delay: 30
Ping_player_join_delay:
Time: 30
Unit: SECONDS
Wait_for_DB_Transactions_on_disable: 20
Wait_for_DB_Transactions_on_disable:
Time: 20
Unit: SECONDS
Thresholds:
# How long player needs to be idle until Plan considers them AFK
AFK_threshold: 3
AFK_threshold:
Time: 3
Unit: MINUTES
# Activity Index considers last 3 weeks and uses these thresholds in the calculation
# The index is a number from 0 to 5.
# These numbers were calibrated with data of 250 players (Small sample size).
Activity_index:
Playtime_threshold: 30
Playtime_threshold:
Time: 30
Unit: MINUTES
Remove_inactive_player_data_after: 180
Remove_inactive_player_data_after:
Time: 180
Unit: DAYS
# Includes players online, tps and performance time series
Remove_time_series_data_after: 90
Remove_time_series_data_after:
Time: 90
Unit: DAYS
Remove_ping_data_after: 14
Remove_ping_data_after:
Time: 14
Unit: DAYS
Remove_disabled_extension_data_after: 2
Remove_disabled_extension_data_after:
Time: 2
Unit: DAYS
Periodic_tasks:
Extension_data_refresh_every: 1
Extension_data_refresh_every:
Time: 1
Unit: HOURS
Check_DB_for_server_config_files_every: 1
Check_DB_for_server_config_files_every:
Time: 1
Unit: MINUTES
Clean_Database_every: 1
Clean_Database_every:
Time: 1
Unit: HOURS
# -----------------------------------------------------
Display_options:
@ -174,8 +193,8 @@ Formatting:
Dates:
# Show_recent_day_names replaces day number with Today, Yesterday, Wednesday etc.
Show_recent_day_names: true
# Non-regex pattern to replace
DatePattern: 'MMM d YYYY'
# Non-regex pattern to replace
Show_recent_day_names_date_pattern: 'MMM d YYYY'
Full: 'MMM d YYYY, HH:mm:ss'
NoSeconds: 'MMM d YYYY, HH:mm'
JustClock: 'HH:mm:ss'
@ -212,7 +231,8 @@ Export:
Export_player_on_login_and_logout: false
# If there are multiple servers the period is divided evenly to avoid export of all servers at once
# Also affects Players page export
Server_refresh_period: 20
Server_refresh_period:
Time: 20
Unit: MINUTES
# -----------------------------------------------------
# These settings affect Plugin data integration.

View File

@ -41,20 +41,25 @@ Database:
# -----------------------------------------------------
Webserver:
Port: 8804
Alternative_IP: false
Alternative_IP:
Enabled: false
# %port% is replaced automatically with Webserver.Port
Address: your.domain.here:%port%
# InternalIP usually does not need to be changed, only change it if you know what you're doing!
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
Internal_IP: 0.0.0.0
Cache:
Reduced_refresh_barrier: 15
Reduced_refresh_barrier:
Time: 15
Unit: SECONDS
Invalidate_query_results_on_disk_after: 7
Invalidate_query_results_on_disk_after:
Time: 7
Unit: DAYS
Invalidate_disk_cache_after: 2
Invalidate_disk_cache_after:
Time: 2
Unit: DAYS
Invalidate_memory_cache_after: 5
Invalidate_memory_cache_after:
Time: 5
Unit: MINUTES
Security:
SSL_certificate:
@ -74,12 +79,14 @@ Webserver:
Access_log:
Print_to_console: false
Remove_logs_after_days: 30
IP_whitelist: false
IP_whitelist:
Enabled: false
Whitelist:
- "192.168.0.0"
- "0:0:0:0:0:0:0:1"
# Does not affect existing cookies
Cookies_expire_after: 2
Cookies_expire_after:
Time: 2
Unit: HOURS
Disable_Webserver: false
External_Webserver_address: https://www.example.address
@ -99,37 +106,49 @@ Data_gathering:
# -----------------------------------------------------
Time:
Delays:
Ping_server_enable_delay: 300
Ping_server_enable_delay:
Time: 300
Unit: SECONDS
Ping_player_join_delay: 30
Ping_player_join_delay:
Time: 30
Unit: SECONDS
Wait_for_DB_Transactions_on_disable: 20
Wait_for_DB_Transactions_on_disable:
Time: 20
Unit: SECONDS
Thresholds:
# How long player needs to be idle until Plan considers them AFK
AFK_threshold: 3
AFK_threshold:
Time: 3
Unit: MINUTES
# Activity Index considers last 3 weeks and uses these thresholds in the calculation
# The index is a number from 0 to 5.
# These numbers were calibrated with data of 250 players (Small sample size).
Activity_index:
Playtime_threshold: 30
Playtime_threshold:
Time: 30
Unit: MINUTES
Remove_inactive_player_data_after: 180
Remove_inactive_player_data_after:
Time: 180
Unit: DAYS
# Includes players online, tps and performance time series
Remove_time_series_data_after: 90
Remove_time_series_data_after:
Time: 90
Unit: DAYS
Remove_ping_data_after: 14
Remove_ping_data_after:
Time: 14
Unit: DAYS
Remove_disabled_extension_data_after: 2
Remove_disabled_extension_data_after:
Time: 2
Unit: DAYS
Periodic_tasks:
Extension_data_refresh_every: 1
Extension_data_refresh_every:
Time: 1
Unit: HOURS
Check_DB_for_server_config_files_every: 1
Check_DB_for_server_config_files_every:
Time: 1
Unit: MINUTES
Clean_Database_every: 1
Clean_Database_every:
Time: 1
Unit: HOURS
# -----------------------------------------------------
Display_options:
@ -179,8 +198,8 @@ Formatting:
Dates:
# Show_recent_day_names replaces day number with Today, Yesterday, Wednesday etc.
Show_recent_day_names: true
# Non-regex pattern to replace
DatePattern: 'MMM d YYYY'
# Non-regex pattern to replace
Show_recent_day_names_date_pattern: 'MMM d YYYY'
Full: 'MMM d YYYY, HH:mm:ss'
NoSeconds: 'MMM d YYYY, HH:mm'
JustClock: HH:mm:ss
@ -218,7 +237,8 @@ Export:
Export_player_on_login_and_logout: false
# If there are multiple servers the period is divided evenly to avoid export of all servers at once
# Also affects Players page export
Server_refresh_period: 20
Server_refresh_period:
Time: 20
Unit: MINUTES
# -----------------------------------------------------
# These settings affect Plugin data integration.

View File

@ -29,6 +29,9 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test to check that configs contain all values required to run the plugin.
@ -58,6 +61,32 @@ class ConfigSettingKeyTest {
TestSettings.assertValidDefaultValuesForAllSettings(config, TestSettings.getProxySettings());
}
@Test
@DisplayName("config.yml key paths don't overlap")
void serverConfigHasUniqueKeyPaths() throws IOException {
PlanConfig config = createConfig("config.yml");
checkConfigPathConflicts(config);
}
@Test
@DisplayName("bungeeconfig.yml key paths don't overlap")
void proxyConfigHasUniqueKeyPaths() throws IOException {
PlanConfig config = createConfig("bungeeconfig.yml");
checkConfigPathConflicts(config);
}
private void checkConfigPathConflicts(PlanConfig config) {
List<Object> result = config.dfs((node, results) -> {
String value = node.getString();
boolean hasChildren = node.getChildren().isEmpty();
boolean hasValue = value != null && !value.isEmpty();
if (!hasChildren && hasValue) {
results.add(node.getKey(true));
}
});
assertTrue(result.isEmpty(), () -> "Following nodes have values and children: " + result);
}
private PlanConfig createConfig(String copyDefaultSettingsFrom) throws IOException {
File configFile = Files.createTempFile(temporaryFolder, "config", ".yml").toFile();
TestResources.copyResourceIntoFile(configFile, "/assets/plan/" + copyDefaultSettingsFrom);

View File

@ -132,4 +132,17 @@ class ConfigChangeTest {
assertFalse(config.contains("Test"), "Old node was not removed");
}
@Test
void valueIsMoved() {
config = prepareConfig("Test: \"value\"");
config.addNode("Test.Child").set("anotherValue");
new ConfigChange.MovedValue("Test", "Test.SecondChild").apply(config);
assertNull(config.getString("Test"));
assertNull(config.getString("Test.SecondChild.Child"));
assertEquals("value", config.getString("Test.SecondChild"));
assertEquals("anotherValue", config.getString("Test.Child"));
}
}